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/SystemData.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/Window.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));
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()

View file

@ -4,81 +4,125 @@
#include "Sound.h"
#include "resources/TextureResource.h"
#include "Log.h"
#include <boost/filesystem.hpp>
#include <boost/assign.hpp>
#include "pugiXML/pugixml.hpp"
#include <boost/assign.hpp>
// Defaults
std::map<std::string, FontDef > ThemeData::sDefaultFonts = boost::assign::map_list_of
("listFont", FontDef(0.045f, ""))
("descriptionFont", FontDef(0.035f, ""))
("fastSelectLetterFont", FontDef(0.15f, ""));
std::map< std::string, std::map<std::string, ThemeData::ElementPropertyType> > ThemeData::sElementMap = boost::assign::map_list_of
("image", boost::assign::map_list_of
("pos", NORMALIZED_PAIR)
("size", NORMALIZED_PAIR)
("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
("listPrimaryColor", 0x0000FFFF)
("listSecondaryColor", 0x00FF00FF)
("listSelectorColor", 0x000000FF)
("listSelectedColor", 0x00000000)
("descriptionColor", 0x48474DFF)
("fastSelectLetterColor", 0xFFFFFFFF)
("fastSelectTextColor", 0xDDDDDDFF);
namespace fs = boost::filesystem;
std::map<std::string, ImageDef> ThemeData::sDefaultImages = boost::assign::map_list_of
("backgroundImage", ImageDef("", true))
("headerImage", ImageDef("", false))
("infoBackgroundImage", ImageDef("", false))
("verticalDividerImage", ImageDef("", false))
("fastSelectBackgroundImage", ImageDef(":/button.png", false))
("systemImage", ImageDef("", false));
#define MINIMUM_THEME_VERSION 3
#define CURRENT_THEME_VERSION 3
std::map<std::string, SoundDef> ThemeData::sDefaultSounds = boost::assign::map_list_of
("scrollSound", SoundDef(""))
("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;
}
// still TODO:
// * how to do <include>?
ThemeData::ThemeData()
{
setDefaults();
std::string defaultDir = getHomePath() + "/.emulationstation/es_theme_default.xml";
if(boost::filesystem::exists(defaultDir))
loadFile(defaultDir);
mVersion = 0;
}
void ThemeData::setDefaults()
void ThemeData::loadFile(const std::string& path)
{
mFontMap.clear();
mImageMap.clear();
mColorMap.clear();
mSoundMap.clear();
ThemeException error;
error.setFile(path);
mFontMap = sDefaultFonts;
mImageMap = sDefaultImages;
mColorMap = sDefaultColors;
mSoundMap = sDefaultSounds;
mPath = path;
if(!fs::exists(path))
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)
return defaultColor;
throw error << "Empty color";
size_t len = strlen(str);
if(len != 6 && len != 8)
{
LOG(LogError) << "Invalid theme color \"" << str << "\" (must be 6 or 8 characters)";
return defaultColor;
}
throw error << "Invalid color (bad length, \"" << str << "\" - must be 6 or 8)";
unsigned int val;
std::stringstream ss;
@ -91,13 +135,12 @@ unsigned int getHexColor(const char* str, unsigned int defaultColor)
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')
return in;
boost::filesystem::path relPath(relative);
relPath = relPath.parent_path();
fs::path relPath = relative.parent_path();
boost::filesystem::path path(in);
@ -114,102 +157,63 @@ std::string resolvePath(const char* in, const std::string& relative)
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))
return;
ThemeException error;
error.setFile(mPath.string());
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(themePath.c_str());
if(!result)
ThemeElement element;
element.extra = root.attribute("extra").as_bool(false);
for(pugi::xml_node node = root.first_child(); node; node = node.next_sibling())
{
LOG(LogWarning) << "Could not parse theme file \"" << themePath << "\":\n " << result.description();
return;
}
auto typeIt = typeMap.find(node.name());
if(typeIt == typeMap.end())
throw error << "Unknown property type \"" << node.name() << "\" (for element of type " << root.name() << ").";
pugi::xml_node root = doc.child("theme");
// Fonts
for(auto it = mFontMap.begin(); it != mFontMap.end(); it++)
{
pugi::xml_node node = root.child(it->first.c_str());
if(node)
switch(typeIt->second)
{
std::string path = resolvePath(node.child("path").text().as_string(it->second.path.c_str()), themePath);
if(!boost::filesystem::exists(path))
{
LOG(LogWarning) << "Font \"" << path << "\" doesn't exist!";
path = it->second.path;
}
case NORMALIZED_PAIR:
{
std::string str = std::string(node.text().as_string());
float size = node.child("size").text().as_float(it->second.size);
mFontMap[it->first] = FontDef(size, path);
root.remove_child(node);
size_t divider = str.find(' ');
if(divider == std::string::npos)
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
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();
return element;
}

View file

@ -1,82 +1,146 @@
#pragma once
#include <iostream>
#include <sstream>
#include <memory>
#include <map>
#include <string>
#include "resources/Font.h"
#include "resources/TextureResource.h"
#include "Renderer.h"
#include "AudioManager.h"
#include "Sound.h"
#include <boost/filesystem.hpp>
#include <boost/variant.hpp>
#include <Eigen/Dense>
#include "pugiXML/pugixml.hpp"
#include "GuiComponent.h"
struct FontDef
template<typename T>
class TextListComponent;
class Sound;
class ImageComponent;
class NinePatchComponent;
class TextComponent;
class Window;
namespace ThemeFlags
{
FontDef() {}
FontDef(float sz, const std::string& p) : path(p), size(sz) {}
enum PropertyFlags : unsigned int
{
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;
float size;
class ThemeException : public std::exception
{
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:
mutable std::shared_ptr<Font> font;
template<typename T>
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() {}
ImageDef(const std::string& p, bool t) : path(p), tile(t) {}
std::string path;
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;
};
std::stringstream ss;
ss << e.msg << appendMsg;
e.msg = ss.str();
return e;
}
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:
static const std::shared_ptr<ThemeData>& getDefault();
ThemeData();
void setDefaults();
// throws ThemeException
void loadFile(const std::string& path);
inline const FontDef& getFontDef(const std::string& identifier) const { return mFontMap.at(identifier); }
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); }
inline unsigned int getColor(const std::string& identifier) const { return mColorMap.at(identifier); }
void playSound(const std::string& identifier) const;
enum ElementPropertyType
{
NORMALIZED_PAIR,
PATH,
STRING,
COLOR,
FLOAT,
BOOLEAN
};
inline void setFont(const std::string& identifier, FontDef def) { mFontMap[identifier] = def; }
inline void setColor(const std::string& identifier, unsigned int color) { mColorMap[identifier] = color; }
void renderExtras(const std::string& view, Window* window, const Eigen::Affine3f& transform);
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:
static std::map<std::string, ImageDef> sDefaultImages;
static std::map<std::string, unsigned int> sDefaultColors;
static std::map<std::string, FontDef > sDefaultFonts;
static std::map<std::string, SoundDef> sDefaultSounds;
static std::map< std::string, std::map<std::string, ElementPropertyType> > sElementMap;
std::map<std::string, ImageDef> mImageMap;
std::map<std::string, unsigned int> mColorMap;
std::map<std::string, FontDef > mFontMap;
std::map< std::string, SoundDef > mSoundMap;
boost::filesystem::path mPath;
float mVersion;
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);
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);
addChild(&mBackground);
mLetterText.setSize(mSize.x(), mSize.y() * 0.75f);
mLetterText.setCentered(true);
mLetterText.setFromTheme(theme, "fastSelectLetterFont", "fastSelectLetterColor");
theme->applyToText("fastSelect", "letter", &mLetterText, FONT_PATH | COLOR);
// TODO - set font size
addChild(&mLetterText);
mSortText.setPosition(0, mSize.y() * 0.75f);
mSortText.setSize(mSize.x(), mSize.y() * 0.25f);
mSortText.setCentered(true);
mSortText.setFromTheme(theme, "descriptionFont", "fastSelectTextColor");
theme->applyToText("fastSelect", "subtext", &mSortText, FONT_PATH | COLOR);
// TODO - set font size
addChild(&mSortText);
mSortId = 0; // TODO

View file

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

View file

@ -23,8 +23,10 @@ public:
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 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 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 setFlipX(bool flip);

View file

@ -138,9 +138,3 @@ std::string TextComponent::getValue() const
{
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;
void setFromTheme(const std::shared_ptr<ThemeData>& theme, const std::string& fontIdentifier, const std::string& colorIdentifier);
private:
void calculateExtent();

View file

@ -13,13 +13,6 @@
#include "../ThemeData.h"
#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.
template <typename T>
class TextListComponent : public GuiComponent
@ -54,16 +47,28 @@ public:
void stopScrolling();
inline bool isScrolling() const { return mScrollDir != 0; }
void setTheme(const std::shared_ptr<ThemeData>& theme);
inline void setCentered(bool centered) { mCentered = centered; }
enum CursorState {
enum CursorState
{
CURSOR_STOPPED,
CURSOR_SCROLLING
};
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:
static const int MARQUEE_DELAY = 900;
static const int MARQUEE_SPEED = 16;
@ -81,13 +86,19 @@ private:
int mMarqueeOffset;
int mMarqueeTime;
std::shared_ptr<ThemeData> mTheme;
bool mCentered;
std::vector<ListRow> mRowVector;
int mCursor;
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>
@ -103,7 +114,11 @@ TextListComponent<T>::TextListComponent(Window* window) :
mCentered = true;
mTheme = ThemeData::getDefault();
mFont = Font::get(FONT_SIZE_MEDIUM);
mSelectorColor = 0x000000FF;
mSelectedColor = 0;
mColors[0] = 0x0000FFFF;
mColors[1] = 0x00FF00FF;
}
template <typename T>
@ -116,7 +131,7 @@ void TextListComponent<T>::render(const Eigen::Affine3f& parentTrans)
{
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 entrySize = font->getHeight() + 5;
@ -157,7 +172,7 @@ void TextListComponent<T>::render(const Eigen::Affine3f& parentTrans)
if(mCursor == i)
{
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);
@ -165,10 +180,10 @@ void TextListComponent<T>::render(const Eigen::Affine3f& parentTrans)
float x = (float)(mCursor == i ? -mMarqueeOffset : 0);
unsigned int color;
if(mCursor == i && mTheme->getColor(THEME_HIGHLIGHTED_COLOR))
color = mTheme->getColor(THEME_HIGHLIGHTED_COLOR);
if(mCursor == i && mSelectedColor)
color = mSelectedColor;
else
color = mTheme->getColor(THEME_ENTRY_COLOR[row.colorId]);
color = mColors[row.colorId];
if(!row.textCache)
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!
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
if(textSize.x() - mMarqueeOffset > getSize().x() - 12)
@ -311,18 +326,16 @@ void TextListComponent<T>::scroll()
}
onCursorChanged(CURSOR_SCROLLING);
mTheme->playSound("scrollSound");
if(mScrollSound)
mScrollSound->play();
}
//list management stuff
template <typename T>
void TextListComponent<T>::add(const std::string& name, const T& obj, unsigned int color)
{
if(color >= THEME_COLOR_ID_COUNT)
{
LOG(LogError) << "Invalid row color Id (" << color << ")";
color = 0;
}
assert(color < COLOR_ID_COUNT);
ListRow row = {name, obj, color};
mRowVector.push_back(row);
@ -392,14 +405,4 @@ void TextListComponent<T>::onCursorChanged(CursorState 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

View file

@ -35,19 +35,23 @@ SystemView::SystemView(Window* window, SystemData* system) : GuiComponent(window
void SystemView::updateData()
{
using namespace ThemeFlags;
mHeaderImage.setImage("");
mSystem->getTheme()->applyToImage("common", "header", &mHeaderImage, PATH);
// header
if(mSystem->getTheme()->getImage("headerImage").path.empty())
if(mHeaderImage.hasImage())
{
// use image
mHeaderText.setText("");
}else{
// use text
mHeaderImage.setImage("");
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)

View file

@ -18,7 +18,8 @@ BasicGameListView::BasicGameListView(Window* window, FileData* root)
void BasicGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& 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)

View file

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

View file

@ -5,19 +5,10 @@
DetailedGameListView::DetailedGameListView(Window* window, FileData* root) :
BasicGameListView(window, root),
mDescContainer(window), mDescription(window),
mImage(window), mInfoBackground(window), mDivider(window)
mImage(window)
{
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;
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())
mHeaderImage.setResize(0, mSize.y() * 0.185f, true);
mDescription.setFont(theme->getFont("descriptionFont"));
mDescription.setColor(theme->getColor("descriptionColor"));
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);
using namespace ThemeFlags;
theme->applyToText("detailed", "description", &mDescription, POSITION | FONT_PATH | FONT_SIZE);
}
void DetailedGameListView::updateInfoPanel()

View file

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

View file

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

View file

@ -36,6 +36,7 @@ public:
virtual bool input(InputConfig* config, Input input) override;
virtual const char* getName() const = 0;
protected:
FileData* mRoot;
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)
{
const ImageDef& bg = theme->getImage("backgroundImage");
mBackground.setTiling(bg.tile);
mBackground.setImage(bg.getTexture());
const ImageDef& hdr = theme->getImage("headerImage");
mHeaderImage.setTiling(hdr.tile);
mHeaderImage.setImage(hdr.getTexture());
using namespace ThemeFlags;
theme->applyToImage("common", "background", &mBackground, PATH | TILING);
theme->applyToImage("common", "header", &mHeaderImage, PATH);
if(mHeaderImage.hasImage())
{
removeChild(&mHeaderText);