First part of the theming system rewrite.

This commit is contained in:
Aloshi 2013-12-30 17:23:34 -06:00
parent 6f442556c0
commit 7f46e50688
19 changed files with 492 additions and 288 deletions

View file

@ -233,6 +233,7 @@ set(ES_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/Sound.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Sound.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/ThemeData.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/ThemeData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/ThemeData_applicators.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Window.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Window.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/XMLReader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/XMLReader.cpp

View file

@ -47,7 +47,14 @@ SystemData::SystemData(const std::string& name, const std::string& fullName, con
mRootFolder->sort(FileSorts::SortTypes.at(0)); mRootFolder->sort(FileSorts::SortTypes.at(0));
mTheme = std::make_shared<ThemeData>(); mTheme = std::make_shared<ThemeData>();
mTheme->loadFile(getThemePath()); try
{
mTheme->loadFile(getThemePath());
} catch(ThemeException& e)
{
LOG(LogError) << e.what();
mTheme = std::make_shared<ThemeData>(); // reset to empty
}
} }
SystemData::~SystemData() SystemData::~SystemData()

View file

@ -4,81 +4,125 @@
#include "Sound.h" #include "Sound.h"
#include "resources/TextureResource.h" #include "resources/TextureResource.h"
#include "Log.h" #include "Log.h"
#include <boost/filesystem.hpp>
#include <boost/assign.hpp>
#include "pugiXML/pugixml.hpp" #include "pugiXML/pugixml.hpp"
#include <boost/assign.hpp>
// Defaults std::map< std::string, std::map<std::string, ThemeData::ElementPropertyType> > ThemeData::sElementMap = boost::assign::map_list_of
std::map<std::string, FontDef > ThemeData::sDefaultFonts = boost::assign::map_list_of ("image", boost::assign::map_list_of
("listFont", FontDef(0.045f, "")) ("pos", NORMALIZED_PAIR)
("descriptionFont", FontDef(0.035f, "")) ("size", NORMALIZED_PAIR)
("fastSelectLetterFont", FontDef(0.15f, "")); ("origin", NORMALIZED_PAIR)
("path", PATH)
("tile", BOOLEAN))
("text", boost::assign::map_list_of
("pos", NORMALIZED_PAIR)
("size", NORMALIZED_PAIR)
("text", STRING)
("color", COLOR)
("fontPath", PATH)
("fontSize", FLOAT)
("center", BOOLEAN))
("textlist", boost::assign::map_list_of
("pos", NORMALIZED_PAIR)
("size", NORMALIZED_PAIR)
("selectorColor", COLOR)
("selectedColor", COLOR)
("primaryColor", COLOR)
("secondaryColor", COLOR)
("fontPath", PATH)
("fontSize", FLOAT))
("sound", boost::assign::map_list_of
("path", PATH));
std::map<std::string, unsigned int> ThemeData::sDefaultColors = boost::assign::map_list_of namespace fs = boost::filesystem;
("listPrimaryColor", 0x0000FFFF)
("listSecondaryColor", 0x00FF00FF)
("listSelectorColor", 0x000000FF)
("listSelectedColor", 0x00000000)
("descriptionColor", 0x48474DFF)
("fastSelectLetterColor", 0xFFFFFFFF)
("fastSelectTextColor", 0xDDDDDDFF);
std::map<std::string, ImageDef> ThemeData::sDefaultImages = boost::assign::map_list_of #define MINIMUM_THEME_VERSION 3
("backgroundImage", ImageDef("", true)) #define CURRENT_THEME_VERSION 3
("headerImage", ImageDef("", false))
("infoBackgroundImage", ImageDef("", false))
("verticalDividerImage", ImageDef("", false))
("fastSelectBackgroundImage", ImageDef(":/button.png", false))
("systemImage", ImageDef("", false));
std::map<std::string, SoundDef> ThemeData::sDefaultSounds = boost::assign::map_list_of // still TODO:
("scrollSound", SoundDef("")) // * how to do <include>?
("gameSelectSound", SoundDef(""))
("backSound", SoundDef(""))
("menuOpenSound", SoundDef(""))
("menuCloseSound", SoundDef(""));
const std::shared_ptr<ThemeData>& ThemeData::getDefault()
{
static const std::shared_ptr<ThemeData> def = std::shared_ptr<ThemeData>(new ThemeData());
return def;
}
ThemeData::ThemeData() ThemeData::ThemeData()
{ {
setDefaults(); mVersion = 0;
std::string defaultDir = getHomePath() + "/.emulationstation/es_theme_default.xml";
if(boost::filesystem::exists(defaultDir))
loadFile(defaultDir);
} }
void ThemeData::setDefaults() void ThemeData::loadFile(const std::string& path)
{ {
mFontMap.clear(); ThemeException error;
mImageMap.clear(); error.setFile(path);
mColorMap.clear();
mSoundMap.clear();
mFontMap = sDefaultFonts; mPath = path;
mImageMap = sDefaultImages;
mColorMap = sDefaultColors; if(!fs::exists(path))
mSoundMap = sDefaultSounds; throw error << "Missing file!";
mVersion = 0;
mViews.clear();
pugi::xml_document doc;
pugi::xml_parse_result res = doc.load_file(path.c_str());
if(!res)
throw error << "XML parsing error: \n " << res.description();
pugi::xml_node root = doc.child("theme");
if(!root)
throw error << "Missing <theme> tag!";
// parse version
mVersion = root.child("version").text().as_float(-404);
if(mVersion == -404)
throw error << "<version> tag missing!\n It's either out of date or you need to add <version>" << CURRENT_THEME_VERSION << "</version> inside your <theme> tag.";
if(mVersion < MINIMUM_THEME_VERSION)
throw error << "Theme is version " << mVersion << ". Minimum supported version is " << MINIMUM_THEME_VERSION << ".";
// parse views
for(pugi::xml_node node = root.child("view"); node; node = node.next_sibling("view"))
{
if(!node.attribute("name"))
throw error << "View missing \"name\" attribute!";
ThemeView view = parseView(node);
if(view.elements.size() > 0)
mViews[node.attribute("name").as_string()] = view;
}
} }
unsigned int getHexColor(const char* str, unsigned int defaultColor) ThemeData::ThemeView ThemeData::parseView(const pugi::xml_node& root)
{ {
ThemeView view;
ThemeException error;
error.setFile(mPath.string());
for(pugi::xml_node node = root.first_child(); node; node = node.next_sibling())
{
if(!node.attribute("name"))
throw error << "Element of type \"" << node.name() << "\" missing \"name\" attribute!";
auto elemTypeIt = sElementMap.find(node.name());
if(elemTypeIt == sElementMap.end())
throw error << "Unknown element of type \"" << node.name() << "\"!";
ThemeElement element = parseElement(node, elemTypeIt->second);
view.elements[node.attribute("name").as_string()] = element;
}
return view;
}
unsigned int getHexColor(const char* str)
{
ThemeException error;
if(!str) if(!str)
return defaultColor; throw error << "Empty color";
size_t len = strlen(str); size_t len = strlen(str);
if(len != 6 && len != 8) if(len != 6 && len != 8)
{ throw error << "Invalid color (bad length, \"" << str << "\" - must be 6 or 8)";
LOG(LogError) << "Invalid theme color \"" << str << "\" (must be 6 or 8 characters)";
return defaultColor;
}
unsigned int val; unsigned int val;
std::stringstream ss; std::stringstream ss;
@ -91,13 +135,12 @@ unsigned int getHexColor(const char* str, unsigned int defaultColor)
return val; return val;
} }
std::string resolvePath(const char* in, const std::string& relative) std::string resolvePath(const char* in, const fs::path& relative)
{ {
if(!in || in[0] == '\0') if(!in || in[0] == '\0')
return in; return in;
boost::filesystem::path relPath(relative); fs::path relPath = relative.parent_path();
relPath = relPath.parent_path();
boost::filesystem::path path(in); boost::filesystem::path path(in);
@ -114,102 +157,63 @@ std::string resolvePath(const char* in, const std::string& relative)
return path.generic_string(); return path.generic_string();
} }
void ThemeData::loadFile(const std::string& themePath)
ThemeData::ThemeElement ThemeData::parseElement(const pugi::xml_node& root, const std::map<std::string, ElementPropertyType>& typeMap)
{ {
if(themePath.empty() || !boost::filesystem::exists(themePath)) ThemeException error;
return; error.setFile(mPath.string());
pugi::xml_document doc; ThemeElement element;
pugi::xml_parse_result result = doc.load_file(themePath.c_str()); element.extra = root.attribute("extra").as_bool(false);
if(!result)
for(pugi::xml_node node = root.first_child(); node; node = node.next_sibling())
{ {
LOG(LogWarning) << "Could not parse theme file \"" << themePath << "\":\n " << result.description(); auto typeIt = typeMap.find(node.name());
return; if(typeIt == typeMap.end())
} throw error << "Unknown property type \"" << node.name() << "\" (for element of type " << root.name() << ").";
pugi::xml_node root = doc.child("theme"); switch(typeIt->second)
// Fonts
for(auto it = mFontMap.begin(); it != mFontMap.end(); it++)
{
pugi::xml_node node = root.child(it->first.c_str());
if(node)
{ {
std::string path = resolvePath(node.child("path").text().as_string(it->second.path.c_str()), themePath); case NORMALIZED_PAIR:
if(!boost::filesystem::exists(path)) {
{ std::string str = std::string(node.text().as_string());
LOG(LogWarning) << "Font \"" << path << "\" doesn't exist!";
path = it->second.path;
}
float size = node.child("size").text().as_float(it->second.size); size_t divider = str.find(' ');
mFontMap[it->first] = FontDef(size, path); if(divider == std::string::npos)
root.remove_child(node); throw error << "invalid normalized pair (\"" << str.c_str() << "\")";
std::string first = str.substr(0, divider);
std::string second = str.substr(divider, std::string::npos);
Eigen::Vector2f val(atof(first.c_str()), atof(second.c_str()));
element.properties[node.name()] = val;
break;
}
case STRING:
element.properties[node.name()] = std::string(node.text().as_string());
break;
case PATH:
{
std::string path = resolvePath(node.text().as_string(), mPath.string());
if(!fs::exists(path))
LOG(LogWarning) << " Warning: theme \"" << mPath << "\" - could not find file \"" << node.text().get() << "\" (resolved to \"" << path << "\")";
element.properties[node.name()] = path;
break;
}
case COLOR:
element.properties[node.name()] = getHexColor(node.text().as_string());
break;
case FLOAT:
element.properties[node.name()] = node.text().as_float();
break;
case BOOLEAN:
element.properties[node.name()] = node.text().as_bool();
break;
default:
throw error << "Unknown ElementPropertyType for " << root.attribute("name").as_string() << " property " << node.name();
} }
} }
// Images return element;
for(auto it = mImageMap.begin(); it != mImageMap.end(); it++)
{
pugi::xml_node node = root.child(it->first.c_str());
if(node)
{
std::string path = resolvePath(node.child("path").text().as_string(it->second.path.c_str()), themePath);
if(!boost::filesystem::exists(path))
{
LOG(LogWarning) << "Image \"" << path << "\" doesn't exist!";
path = it->second.path;
}
bool tile = node.child("tile").text().as_bool(it->second.tile);
mImageMap[it->first] = ImageDef(path, tile);
root.remove_child(node);
}
}
// Colors
for(auto it = mColorMap.begin(); it != mColorMap.end(); it++)
{
pugi::xml_node node = root.child(it->first.c_str());
if(node)
{
mColorMap[it->first] = getHexColor(node.text().as_string(NULL), it->second);
root.remove_child(node);
}
}
// Sounds
for(auto it = mSoundMap.begin(); it != mSoundMap.end(); it++)
{
pugi::xml_node node = root.child(it->first.c_str());
if(node)
{
std::string path = resolvePath(node.text().as_string(it->second.path.c_str()), themePath);
if(!boost::filesystem::exists(path))
{
LOG(LogWarning) << "Sound \"" << path << "\" doesn't exist!";
path = it->second.path;
}
mSoundMap[it->first] = SoundDef(path);
root.remove_child(node);
}
}
if(root.begin() != root.end())
{
std::stringstream ss;
ss << "Unused theme identifiers:\n";
for(auto it = root.children().begin(); it != root.children().end(); it++)
{
ss << " " << it->name() << "\n";
}
LOG(LogWarning) << ss.str();
}
}
void ThemeData::playSound(const std::string& identifier) const
{
mSoundMap.at(identifier).get()->play();
} }

View file

@ -1,82 +1,146 @@
#pragma once #pragma once
#include <iostream>
#include <sstream>
#include <memory> #include <memory>
#include <map> #include <map>
#include <string> #include <string>
#include "resources/Font.h" #include <boost/filesystem.hpp>
#include "resources/TextureResource.h" #include <boost/variant.hpp>
#include "Renderer.h" #include <Eigen/Dense>
#include "AudioManager.h" #include "pugiXML/pugixml.hpp"
#include "Sound.h" #include "GuiComponent.h"
struct FontDef template<typename T>
class TextListComponent;
class Sound;
class ImageComponent;
class NinePatchComponent;
class TextComponent;
class Window;
namespace ThemeFlags
{ {
FontDef() {} enum PropertyFlags : unsigned int
FontDef(float sz, const std::string& p) : path(p), size(sz) {} {
PATH = 1,
POSITION = 2,
SIZE = 4,
ORIGIN = 8,
COLOR = 16,
FONT_PATH = 32,
FONT_SIZE = 64,
TILING = 128,
SOUND = 256,
CENTER = 512,
TEXT = 1024
};
}
std::string path; class ThemeException : public std::exception
float size; {
protected:
std::string msg;
inline const std::shared_ptr<Font>& get() const { if(!font) font = Font::get((int)(size * Renderer::getScreenHeight()), path); return font; } public:
virtual const char* what() const throw() { return msg.c_str(); }
private: template<typename T>
mutable std::shared_ptr<Font> font; friend ThemeException& operator<<(ThemeException& e, T msg);
inline void setFile(const std::string& filename) { *this << "Error loading theme from \"" << filename << "\":\n "; }
}; };
struct ImageDef template<typename T>
ThemeException& operator<<(ThemeException& e, T appendMsg)
{ {
ImageDef() {} std::stringstream ss;
ImageDef(const std::string& p, bool t) : path(p), tile(t) {} ss << e.msg << appendMsg;
e.msg = ss.str();
std::string path; return e;
bool tile; }
inline const std::shared_ptr<TextureResource>& getTexture() const { if(!texture && !path.empty()) texture = TextureResource::get(path); return texture; }
private:
mutable std::shared_ptr<TextureResource> texture;
};
struct SoundDef
{
SoundDef() {}
SoundDef(const std::string& p) : path(p) { sound = std::shared_ptr<Sound>(new Sound(path)); AudioManager::getInstance()->registerSound(sound); }
std::string path;
inline const std::shared_ptr<Sound>& get() const { return sound; }
private:
std::shared_ptr<Sound> sound;
};
class ThemeData class ThemeData
{ {
private:
class ThemeElement
{
public:
bool extra;
std::string type;
std::map< std::string, boost::variant<Eigen::Vector2f, std::string, unsigned int, float, bool> > properties;
template<typename T>
T get(const std::string& prop) { return boost::get<T>(properties.at(prop)); }
inline bool has(const std::string& prop) { return (properties.find(prop) != properties.end()); }
};
class ThemeView
{
public:
ThemeView() : mExtrasDirty(true) {}
virtual ~ThemeView() { for(auto it = mExtras.begin(); it != mExtras.end(); it++) delete *it; }
std::map<std::string, ThemeElement> elements;
const std::vector<GuiComponent*>& getExtras(Window* window);
private:
bool mExtrasDirty;
std::vector<GuiComponent*> mExtras;
};
public: public:
static const std::shared_ptr<ThemeData>& getDefault();
ThemeData(); ThemeData();
void setDefaults(); // throws ThemeException
void loadFile(const std::string& path); void loadFile(const std::string& path);
inline const FontDef& getFontDef(const std::string& identifier) const { return mFontMap.at(identifier); } enum ElementPropertyType
inline std::shared_ptr<Font> getFont(const std::string& identifier) const { return mFontMap.at(identifier).get(); } {
inline const ImageDef& getImage(const std::string& identifier) const { return mImageMap.at(identifier); } NORMALIZED_PAIR,
inline unsigned int getColor(const std::string& identifier) const { return mColorMap.at(identifier); } PATH,
void playSound(const std::string& identifier) const; STRING,
COLOR,
FLOAT,
BOOLEAN
};
inline void setFont(const std::string& identifier, FontDef def) { mFontMap[identifier] = def; } void renderExtras(const std::string& view, Window* window, const Eigen::Affine3f& transform);
inline void setColor(const std::string& identifier, unsigned int color) { mColorMap[identifier] = color; }
void applyToImage(const std::string& view, const std::string& element, ImageComponent* image, unsigned int properties);
void applyToNinePatch(const std::string& view, const std::string& element, NinePatchComponent* patch, unsigned int properties);
void applyToText(const std::string& view, const std::string& element, TextComponent* text, unsigned int properties);
template <typename T>
void applyToTextList(const std::string& view, const std::string& element, TextListComponent<T>* list, unsigned int properties);
void playSound(const std::string& name);
private: private:
static std::map<std::string, ImageDef> sDefaultImages; static std::map< std::string, std::map<std::string, ElementPropertyType> > sElementMap;
static std::map<std::string, unsigned int> sDefaultColors;
static std::map<std::string, FontDef > sDefaultFonts;
static std::map<std::string, SoundDef> sDefaultSounds;
std::map<std::string, ImageDef> mImageMap; boost::filesystem::path mPath;
std::map<std::string, unsigned int> mColorMap; float mVersion;
std::map<std::string, FontDef > mFontMap;
std::map< std::string, SoundDef > mSoundMap; ThemeView parseView(const pugi::xml_node& view);
ThemeElement parseElement(const pugi::xml_node& element, const std::map<std::string, ElementPropertyType>& typeMap);
ThemeElement* getElement(const std::string& viewName, const std::string& elementName);
std::map<std::string, ThemeView> mViews;
std::map< std::string, std::shared_ptr<Sound> > mSoundCache;
}; };
template <typename T>
void ThemeData::applyToTextList(const std::string& view, const std::string& element, TextListComponent<T>* list, unsigned int properties)
{
}

View file

@ -0,0 +1,135 @@
#include "ThemeData.h"
#include "Renderer.h"
#include "components/ImageComponent.h"
#include "components/NinePatchComponent.h"
#include "components/TextComponent.h"
#include "Sound.h"
#include "Log.h"
using namespace ThemeFlags;
Eigen::Vector2f getScale(GuiComponent* comp)
{
if(comp && comp->getParent())
return comp->getParent()->getSize();
return Eigen::Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
}
ThemeData::ThemeElement* ThemeData::getElement(const std::string& viewName, const std::string& elementName)
{
auto viewIt = mViews.find(viewName);
if(viewIt == mViews.end())
return NULL;
auto elemIt = viewIt->second.elements.find(elementName);
if(elemIt == viewIt->second.elements.end())
return NULL;
return &elemIt->second;
}
void ThemeData::applyToImage(const std::string& viewName, const std::string& elementName, ImageComponent* image, unsigned int properties)
{
LOG(LogInfo) << " req image [" << viewName << "." << elementName << "] (flags: " << properties << ")";
ThemeElement* elem = getElement(viewName, elementName);
if(!elem)
{
LOG(LogInfo) << " (missing)";
return;
}
Eigen::Vector2f scale = getScale(image);
if(properties & POSITION && elem->has("pos"))
{
Eigen::Vector2f denormalized = elem->get<Eigen::Vector2f>("pos").cwiseProduct(scale);
image->setPosition(Eigen::Vector3f(denormalized.x(), denormalized.y(), 0));
}
if(properties & ThemeFlags::SIZE && elem->has("size"))
image->setResize(elem->get<Eigen::Vector2f>("size").cwiseProduct(scale), true);
if(properties & ORIGIN && elem->has("origin"))
image->setOrigin(elem->get<Eigen::Vector2f>("origin"));
if(properties & PATH && elem->has("path"))
image->setImage(elem->get<std::string>("path"));
if(properties & TILING && elem->has("tile"))
image->setTiling(elem->get<bool>("tile"));
}
void ThemeData::applyToNinePatch(const std::string& viewName, const std::string& elementName, NinePatchComponent* patch, unsigned int properties)
{
ThemeElement* elem = getElement(viewName, elementName);
if(!elem)
return;
Eigen::Vector2f scale = getScale(patch);
if(properties & POSITION && elem->has("pos"))
{
Eigen::Vector2f denormalized = elem->get<Eigen::Vector2f>("pos").cwiseProduct(scale);
patch->setPosition(Eigen::Vector3f(denormalized.x(), denormalized.y(), 0));
}
if(properties & PATH && elem->has("path"))
patch->setImagePath(elem->get<std::string>("path"));
if(properties & ThemeFlags::SIZE && elem->has("size"))
patch->setSize(elem->get<Eigen::Vector2f>("size").cwiseProduct(scale));
}
void ThemeData::applyToText(const std::string& viewName, const std::string& elementName, TextComponent* text, unsigned int properties)
{
ThemeElement* elem = getElement(viewName, elementName);
if(!elem)
return;
Eigen::Vector2f scale = getScale(text);
if(properties & POSITION && elem->has("pos"))
{
Eigen::Vector2f denormalized = elem->get<Eigen::Vector2f>("pos").cwiseProduct(scale);
text->setPosition(Eigen::Vector3f(denormalized.x(), denormalized.y(), 0));
}
if(properties & ThemeFlags::SIZE && elem->has("size"))
text->setSize(elem->get<Eigen::Vector2f>("size").cwiseProduct(scale));
if(properties & COLOR && elem->has("color"))
text->setColor(elem->get<unsigned int>("color"));
if(properties & CENTER && elem->has("center"))
text->setCentered(elem->get<bool>("center"));
if(properties & TEXT && elem->has("text"))
text->setText(elem->get<std::string>("text"));
// TODO - fonts
}
void ThemeData::playSound(const std::string& elementName)
{
ThemeElement* elem = getElement("common", elementName);
if(!elem)
return;
if(elem->has("path"))
{
const std::string path = elem->get<std::string>("path");
auto cacheIt = mSoundCache.find(path);
if(cacheIt != mSoundCache.end())
{
cacheIt->second->play();
return;
}
std::shared_ptr<Sound> sound = std::shared_ptr<Sound>(new Sound(path));
sound->play();
mSoundCache[path] = sound;
}
}

View file

@ -12,20 +12,23 @@ GuiFastSelect::GuiFastSelect(Window* window, IGameListView* gamelist) : GuiCompo
setSize(Renderer::getScreenWidth() * 0.6f, Renderer::getScreenHeight() * 0.6f); setSize(Renderer::getScreenWidth() * 0.6f, Renderer::getScreenHeight() * 0.6f);
const std::shared_ptr<ThemeData>& theme = mGameList->getTheme(); const std::shared_ptr<ThemeData>& theme = mGameList->getTheme();
using namespace ThemeFlags;
mBackground.setImagePath(theme->getImage("fastSelectBackgroundImage").path); theme->applyToNinePatch("fastSelect", "background", &mBackground, PATH);
mBackground.fitTo(mSize); mBackground.fitTo(mSize);
addChild(&mBackground); addChild(&mBackground);
mLetterText.setSize(mSize.x(), mSize.y() * 0.75f); mLetterText.setSize(mSize.x(), mSize.y() * 0.75f);
mLetterText.setCentered(true); mLetterText.setCentered(true);
mLetterText.setFromTheme(theme, "fastSelectLetterFont", "fastSelectLetterColor"); theme->applyToText("fastSelect", "letter", &mLetterText, FONT_PATH | COLOR);
// TODO - set font size
addChild(&mLetterText); addChild(&mLetterText);
mSortText.setPosition(0, mSize.y() * 0.75f); mSortText.setPosition(0, mSize.y() * 0.75f);
mSortText.setSize(mSize.x(), mSize.y() * 0.25f); mSortText.setSize(mSize.x(), mSize.y() * 0.25f);
mSortText.setCentered(true); mSortText.setCentered(true);
mSortText.setFromTheme(theme, "descriptionFont", "fastSelectTextColor"); theme->applyToText("fastSelect", "subtext", &mSortText, FONT_PATH | COLOR);
// TODO - set font size
addChild(&mSortText); addChild(&mSortText);
mSortId = 0; // TODO mSortId = 0; // TODO

View file

@ -39,11 +39,12 @@ GuiMenu::GuiMenu(Window* window) : GuiComponent(window), mBackground(window, ":/
addChild(&mBackground); addChild(&mBackground);
mTheme = std::make_shared<ThemeData>(); mTheme = std::make_shared<ThemeData>();
mTheme->setFont("listFont", FontDef(0.09f, mTheme->getFontDef("listFont").path)); using namespace ThemeFlags;
mTheme->setColor("listSelectorColor", 0xBBBBBBFF); mTheme->applyToTextList< std::function<void()> >("common", "menu", &mList, FONT_PATH | COLOR);
mTheme->setColor("listPrimaryColor", 0x0000FFFF); mList.setSelectorColor(0xBBBBBBFF);
mTheme->setColor("listSecondaryColor", 0xFF0000FF); mList.setColor(0, 0x0000FFFF);
mList.setTheme(mTheme); mList.setColor(1, 0xFF0000FF);
// TODO - set font size to 0.09f
addChild(&mList); addChild(&mList);
} }

View file

@ -23,8 +23,10 @@ public:
void setImage(const char* image, size_t length); //Loads image from memory. void setImage(const char* image, size_t length); //Loads image from memory.
void setImage(const std::shared_ptr<TextureResource>& texture); //Use an already existing texture. void setImage(const std::shared_ptr<TextureResource>& texture); //Use an already existing texture.
void setOrigin(float originX, float originY); //Sets the origin as a percentage of this image (e.g. (0, 0) is top left, (0.5, 0.5) is the center) void setOrigin(float originX, float originY); //Sets the origin as a percentage of this image (e.g. (0, 0) is top left, (0.5, 0.5) is the center)
inline void setOrigin(Eigen::Vector2f origin) { setOrigin(origin.x(), origin.y()); }
void setTiling(bool tile); //Enables or disables tiling. Must be called before loading an image or resizing will be weird. void setTiling(bool tile); //Enables or disables tiling. Must be called before loading an image or resizing will be weird.
void setResize(float width, float height, bool allowUpscale); void setResize(float width, float height, bool allowUpscale);
inline void setResize(Eigen::Vector2f size, bool allowUpscale) { setResize(size.x(), size.y(), allowUpscale); }
void setColorShift(unsigned int color); void setColorShift(unsigned int color);
void setFlipX(bool flip); void setFlipX(bool flip);

View file

@ -138,9 +138,3 @@ std::string TextComponent::getValue() const
{ {
return mText; return mText;
} }
void TextComponent::setFromTheme(const std::shared_ptr<ThemeData>& theme, const std::string& fontIdentifier, const std::string& colorIdentifier)
{
setFont(theme->getFont(fontIdentifier));
setColor(theme->getColor(colorIdentifier));
}

View file

@ -28,8 +28,6 @@ public:
std::shared_ptr<Font> getFont() const; std::shared_ptr<Font> getFont() const;
void setFromTheme(const std::shared_ptr<ThemeData>& theme, const std::string& fontIdentifier, const std::string& colorIdentifier);
private: private:
void calculateExtent(); void calculateExtent();

View file

@ -13,13 +13,6 @@
#include "../ThemeData.h" #include "../ThemeData.h"
#include <functional> #include <functional>
#define THEME_FONT "listFont"
#define THEME_SELECTOR_COLOR "listSelectorColor"
#define THEME_HIGHLIGHTED_COLOR "listSelectedColor"
#define THEME_SCROLL_SOUND "listScrollSound"
static const int THEME_COLOR_ID_COUNT = 2;
static const char* const THEME_ENTRY_COLOR[THEME_COLOR_ID_COUNT] = { "listPrimaryColor", "listSecondaryColor" };
//A graphical list. Supports multiple colors for rows and scrolling. //A graphical list. Supports multiple colors for rows and scrolling.
template <typename T> template <typename T>
class TextListComponent : public GuiComponent class TextListComponent : public GuiComponent
@ -54,16 +47,28 @@ public:
void stopScrolling(); void stopScrolling();
inline bool isScrolling() const { return mScrollDir != 0; } inline bool isScrolling() const { return mScrollDir != 0; }
void setTheme(const std::shared_ptr<ThemeData>& theme);
inline void setCentered(bool centered) { mCentered = centered; } inline void setCentered(bool centered) { mCentered = centered; }
enum CursorState { enum CursorState
{
CURSOR_STOPPED, CURSOR_STOPPED,
CURSOR_SCROLLING CURSOR_SCROLLING
}; };
inline void setCursorChangedCallback(const std::function<void(CursorState state)>& func) { mCursorChangedCallback = func; } inline void setCursorChangedCallback(const std::function<void(CursorState state)>& func) { mCursorChangedCallback = func; }
inline void setFont(const std::shared_ptr<Font>& font)
{
mFont = font;
for(auto it = mRowVector.begin(); it != mRowVector.end(); it++)
it->textCache.reset();
}
inline void setSelectorColor(unsigned int color) { mSelectorColor = color; }
inline void setSelectedColor(unsigned int color) { mSelectedColor = color; }
inline void setScrollSound(const std::shared_ptr<Sound>& sound) { mScrollSound = sound; }
inline void setColor(unsigned int id, unsigned int color) { mColors[id] = color; }
private: private:
static const int MARQUEE_DELAY = 900; static const int MARQUEE_DELAY = 900;
static const int MARQUEE_SPEED = 16; static const int MARQUEE_SPEED = 16;
@ -81,13 +86,19 @@ private:
int mMarqueeOffset; int mMarqueeOffset;
int mMarqueeTime; int mMarqueeTime;
std::shared_ptr<ThemeData> mTheme;
bool mCentered; bool mCentered;
std::vector<ListRow> mRowVector; std::vector<ListRow> mRowVector;
int mCursor; int mCursor;
std::function<void(CursorState state)> mCursorChangedCallback; std::function<void(CursorState state)> mCursorChangedCallback;
std::shared_ptr<Font> mFont;
unsigned int mSelectorColor;
unsigned int mSelectedColor;
std::shared_ptr<Sound> mScrollSound;
static const unsigned int COLOR_ID_COUNT = 2;
unsigned int mColors[COLOR_ID_COUNT];
}; };
template <typename T> template <typename T>
@ -103,7 +114,11 @@ TextListComponent<T>::TextListComponent(Window* window) :
mCentered = true; mCentered = true;
mTheme = ThemeData::getDefault(); mFont = Font::get(FONT_SIZE_MEDIUM);
mSelectorColor = 0x000000FF;
mSelectedColor = 0;
mColors[0] = 0x0000FFFF;
mColors[1] = 0x00FF00FF;
} }
template <typename T> template <typename T>
@ -116,7 +131,7 @@ void TextListComponent<T>::render(const Eigen::Affine3f& parentTrans)
{ {
Eigen::Affine3f trans = parentTrans * getTransform(); Eigen::Affine3f trans = parentTrans * getTransform();
std::shared_ptr<Font> font = mTheme->getFont(THEME_FONT); std::shared_ptr<Font>& font = mFont;
const int cutoff = 0; const int cutoff = 0;
const int entrySize = font->getHeight() + 5; const int entrySize = font->getHeight() + 5;
@ -157,7 +172,7 @@ void TextListComponent<T>::render(const Eigen::Affine3f& parentTrans)
if(mCursor == i) if(mCursor == i)
{ {
Renderer::setMatrix(trans); Renderer::setMatrix(trans);
Renderer::drawRect(0, (int)y, (int)getSize().x(), font->getHeight(), mTheme->getColor(THEME_SELECTOR_COLOR)); Renderer::drawRect(0, (int)y, (int)getSize().x(), font->getHeight(), mSelectorColor);
} }
ListRow& row = mRowVector.at((unsigned int)i); ListRow& row = mRowVector.at((unsigned int)i);
@ -165,10 +180,10 @@ void TextListComponent<T>::render(const Eigen::Affine3f& parentTrans)
float x = (float)(mCursor == i ? -mMarqueeOffset : 0); float x = (float)(mCursor == i ? -mMarqueeOffset : 0);
unsigned int color; unsigned int color;
if(mCursor == i && mTheme->getColor(THEME_HIGHLIGHTED_COLOR)) if(mCursor == i && mSelectedColor)
color = mTheme->getColor(THEME_HIGHLIGHTED_COLOR); color = mSelectedColor;
else else
color = mTheme->getColor(THEME_ENTRY_COLOR[row.colorId]); color = mColors[row.colorId];
if(!row.textCache) if(!row.textCache)
row.textCache = std::unique_ptr<TextCache>(font->buildTextCache(row.name, 0, 0, 0x000000FF)); row.textCache = std::unique_ptr<TextCache>(font->buildTextCache(row.name, 0, 0, 0x000000FF));
@ -273,7 +288,7 @@ void TextListComponent<T>::update(int deltaTime)
//if we're not scrolling and this object's text goes outside our size, marquee it! //if we're not scrolling and this object's text goes outside our size, marquee it!
std::string text = getSelectedName(); std::string text = getSelectedName();
Eigen::Vector2f textSize = mTheme->getFont(THEME_FONT)->sizeText(text); Eigen::Vector2f textSize = mFont->sizeText(text);
//it's long enough to marquee //it's long enough to marquee
if(textSize.x() - mMarqueeOffset > getSize().x() - 12) if(textSize.x() - mMarqueeOffset > getSize().x() - 12)
@ -311,18 +326,16 @@ void TextListComponent<T>::scroll()
} }
onCursorChanged(CURSOR_SCROLLING); onCursorChanged(CURSOR_SCROLLING);
mTheme->playSound("scrollSound");
if(mScrollSound)
mScrollSound->play();
} }
//list management stuff //list management stuff
template <typename T> template <typename T>
void TextListComponent<T>::add(const std::string& name, const T& obj, unsigned int color) void TextListComponent<T>::add(const std::string& name, const T& obj, unsigned int color)
{ {
if(color >= THEME_COLOR_ID_COUNT) assert(color < COLOR_ID_COUNT);
{
LOG(LogError) << "Invalid row color Id (" << color << ")";
color = 0;
}
ListRow row = {name, obj, color}; ListRow row = {name, obj, color};
mRowVector.push_back(row); mRowVector.push_back(row);
@ -392,14 +405,4 @@ void TextListComponent<T>::onCursorChanged(CursorState state)
mCursorChangedCallback(state); mCursorChangedCallback(state);
} }
template <typename T>
void TextListComponent<T>::setTheme(const std::shared_ptr<ThemeData>& theme)
{
mTheme = theme;
// invalidate text caches in case font changed
for(auto it = mRowVector.begin(); it != mRowVector.end(); it++)
it->textCache.reset();
}
#endif #endif

View file

@ -35,19 +35,23 @@ SystemView::SystemView(Window* window, SystemData* system) : GuiComponent(window
void SystemView::updateData() void SystemView::updateData()
{ {
using namespace ThemeFlags;
mHeaderImage.setImage("");
mSystem->getTheme()->applyToImage("common", "header", &mHeaderImage, PATH);
// header // header
if(mSystem->getTheme()->getImage("headerImage").path.empty()) if(mHeaderImage.hasImage())
{ {
// use image
mHeaderText.setText("");
}else{
// use text // use text
mHeaderImage.setImage(""); mHeaderImage.setImage("");
mHeaderText.setText(mSystem->getFullName()); mHeaderText.setText(mSystem->getFullName());
}else{
// use image
mHeaderText.setText("");
mHeaderImage.setImage(mSystem->getTheme()->getImage("headerImage").getTexture());
} }
mImage.setImage(mSystem->getTheme()->getImage("systemImage").getTexture()); mSystem->getTheme()->applyToImage("common", "system", &mImage, PATH);
} }
bool SystemView::input(InputConfig* config, Input input) bool SystemView::input(InputConfig* config, Input input)

View file

@ -18,7 +18,8 @@ BasicGameListView::BasicGameListView(Window* window, FileData* root)
void BasicGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme) void BasicGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
{ {
ISimpleGameListView::onThemeChanged(theme); ISimpleGameListView::onThemeChanged(theme);
mList.setTheme(theme); using namespace ThemeFlags;
theme->applyToTextList(getName(), "gamelist", &mList, POSITION | ThemeFlags::SIZE | COLOR | SOUND);
} }
void BasicGameListView::onFileChanged(FileData* file, FileChangeType change) void BasicGameListView::onFileChanged(FileData* file, FileChangeType change)

View file

@ -16,6 +16,8 @@ public:
virtual FileData* getCursor() override; virtual FileData* getCursor() override;
virtual void setCursor(FileData* file) override; virtual void setCursor(FileData* file) override;
virtual const char* getName() const override { return "basic"; }
protected: protected:
virtual void populateList(const std::vector<FileData*>& files) override; virtual void populateList(const std::vector<FileData*>& files) override;
virtual void launch(FileData* game) override; virtual void launch(FileData* game) override;

View file

@ -5,19 +5,10 @@
DetailedGameListView::DetailedGameListView(Window* window, FileData* root) : DetailedGameListView::DetailedGameListView(Window* window, FileData* root) :
BasicGameListView(window, root), BasicGameListView(window, root),
mDescContainer(window), mDescription(window), mDescContainer(window), mDescription(window),
mImage(window), mInfoBackground(window), mDivider(window) mImage(window)
{ {
mHeaderImage.setPosition(mSize.x() * 0.25f, 0); mHeaderImage.setPosition(mSize.x() * 0.25f, 0);
mInfoBackground.setPosition(0, mSize.y() * 0.5f);
mInfoBackground.setOrigin(0, 0.5f);
mInfoBackground.setResize(mSize.x() * 0.5f, mSize.y(), true);
addChild(&mInfoBackground);
mDivider.setPosition(mSize.x() * 0.5f, mSize.y() * 0.5f);
mDivider.setOrigin(0.5f, 0.5f);
addChild(&mDivider);
const float padding = 0.01f; const float padding = 0.01f;
mList.setPosition(mSize.x() * (0.50f + padding), mList.getPosition().y()); mList.setPosition(mSize.x() * (0.50f + padding), mList.getPosition().y());
@ -49,13 +40,8 @@ void DetailedGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& them
if(mHeaderImage.getPosition().y() + mHeaderImage.getSize().y() > mImage.getPosition().y()) if(mHeaderImage.getPosition().y() + mHeaderImage.getSize().y() > mImage.getPosition().y())
mHeaderImage.setResize(0, mSize.y() * 0.185f, true); mHeaderImage.setResize(0, mSize.y() * 0.185f, true);
mDescription.setFont(theme->getFont("descriptionFont")); using namespace ThemeFlags;
mDescription.setColor(theme->getColor("descriptionColor")); theme->applyToText("detailed", "description", &mDescription, POSITION | FONT_PATH | FONT_SIZE);
mInfoBackground.setImage(theme->getImage("infoBackgroundImage").getTexture());
mInfoBackground.setTiling(theme->getImage("infoBackgroundImage").tile);
mDivider.setImage(theme->getImage("verticalDividerImage").getTexture());
mDivider.setResize((float)mDivider.getTextureSize().x(), mSize.y(), true);
} }
void DetailedGameListView::updateInfoPanel() void DetailedGameListView::updateInfoPanel()

View file

@ -10,6 +10,8 @@ public:
virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override; virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override;
virtual const char* getName() const override { return "detailed"; }
protected: protected:
virtual void launch(FileData* game) override; virtual void launch(FileData* game) override;
@ -17,9 +19,7 @@ private:
void updateInfoPanel(); void updateInfoPanel();
ImageComponent mImage; ImageComponent mImage;
ImageComponent mInfoBackground;
ImageComponent mDivider;
ScrollableContainer mDescContainer; ScrollableContainer mDescContainer;
TextComponent mDescription; TextComponent mDescription;
}; };

View file

@ -17,6 +17,8 @@ public:
virtual bool input(InputConfig* config, Input input) override; virtual bool input(InputConfig* config, Input input) override;
virtual const char* getName() const override { return "grid"; }
protected: protected:
virtual void populateList(const std::vector<FileData*>& files) override; virtual void populateList(const std::vector<FileData*>& files) override;
virtual void launch(FileData* game) override; virtual void launch(FileData* game) override;

View file

@ -36,6 +36,7 @@ public:
virtual bool input(InputConfig* config, Input input) override; virtual bool input(InputConfig* config, Input input) override;
virtual const char* getName() const = 0;
protected: protected:
FileData* mRoot; FileData* mRoot;
std::shared_ptr<ThemeData> mTheme; std::shared_ptr<ThemeData> mTheme;

View file

@ -23,14 +23,10 @@ ISimpleGameListView::ISimpleGameListView(Window* window, FileData* root) : IGame
void ISimpleGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme) void ISimpleGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
{ {
const ImageDef& bg = theme->getImage("backgroundImage"); using namespace ThemeFlags;
mBackground.setTiling(bg.tile); theme->applyToImage("common", "background", &mBackground, PATH | TILING);
mBackground.setImage(bg.getTexture()); theme->applyToImage("common", "header", &mHeaderImage, PATH);
const ImageDef& hdr = theme->getImage("headerImage");
mHeaderImage.setTiling(hdr.tile);
mHeaderImage.setImage(hdr.getTexture());
if(mHeaderImage.hasImage()) if(mHeaderImage.hasImage())
{ {
removeChild(&mHeaderText); removeChild(&mHeaderText);