diff --git a/CMakeLists.txt b/CMakeLists.txt index dafd0fd26..5f01347c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/SystemData.cpp b/src/SystemData.cpp index e7055d510..187b223e8 100644 --- a/src/SystemData.cpp +++ b/src/SystemData.cpp @@ -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(); - mTheme->loadFile(getThemePath()); + try + { + mTheme->loadFile(getThemePath()); + } catch(ThemeException& e) + { + LOG(LogError) << e.what(); + mTheme = std::make_shared(); // reset to empty + } } SystemData::~SystemData() diff --git a/src/ThemeData.cpp b/src/ThemeData.cpp index 229dd18fb..89169779d 100644 --- a/src/ThemeData.cpp +++ b/src/ThemeData.cpp @@ -4,81 +4,125 @@ #include "Sound.h" #include "resources/TextureResource.h" #include "Log.h" -#include -#include #include "pugiXML/pugixml.hpp" +#include -// Defaults -std::map 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 > 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 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 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 ThemeData::sDefaultSounds = boost::assign::map_list_of - ("scrollSound", SoundDef("")) - ("gameSelectSound", SoundDef("")) - ("backSound", SoundDef("")) - ("menuOpenSound", SoundDef("")) - ("menuCloseSound", SoundDef("")); - - - -const std::shared_ptr& ThemeData::getDefault() -{ - static const std::shared_ptr def = std::shared_ptr(new ThemeData()); - return def; -} +// still TODO: +// * how to do ? 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 tag!"; + + // parse version + mVersion = root.child("version").text().as_float(-404); + if(mVersion == -404) + throw error << " tag missing!\n It's either out of date or you need to add " << CURRENT_THEME_VERSION << " inside your 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& 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; } diff --git a/src/ThemeData.h b/src/ThemeData.h index 572e6c63e..cf2cfb897 100644 --- a/src/ThemeData.h +++ b/src/ThemeData.h @@ -1,82 +1,146 @@ #pragma once +#include +#include #include #include #include -#include "resources/Font.h" -#include "resources/TextureResource.h" -#include "Renderer.h" -#include "AudioManager.h" -#include "Sound.h" +#include +#include +#include +#include "pugiXML/pugixml.hpp" +#include "GuiComponent.h" -struct FontDef +template +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& 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; + template + friend ThemeException& operator<<(ThemeException& e, T msg); + + inline void setFile(const std::string& filename) { *this << "Error loading theme from \"" << filename << "\":\n "; } }; -struct ImageDef +template +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& getTexture() const { if(!texture && !path.empty()) texture = TextureResource::get(path); return texture; } - -private: - mutable std::shared_ptr texture; -}; - -struct SoundDef -{ - SoundDef() {} - SoundDef(const std::string& p) : path(p) { sound = std::shared_ptr(new Sound(path)); AudioManager::getInstance()->registerSound(sound); } - - std::string path; - - inline const std::shared_ptr& get() const { return sound; } - -private: - std::shared_ptr 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 > properties; + + template + T get(const std::string& prop) { return boost::get(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 elements; + + const std::vector& getExtras(Window* window); + + private: + bool mExtrasDirty; + std::vector mExtras; + }; + public: - static const std::shared_ptr& 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 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 + void applyToTextList(const std::string& view, const std::string& element, TextListComponent* list, unsigned int properties); + + void playSound(const std::string& name); private: - static std::map sDefaultImages; - static std::map sDefaultColors; - static std::map sDefaultFonts; - static std::map sDefaultSounds; + static std::map< std::string, std::map > sElementMap; - std::map mImageMap; - std::map mColorMap; - std::map 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& typeMap); + + ThemeElement* getElement(const std::string& viewName, const std::string& elementName); + + std::map mViews; + + std::map< std::string, std::shared_ptr > mSoundCache; }; + + +template +void ThemeData::applyToTextList(const std::string& view, const std::string& element, TextListComponent* list, unsigned int properties) +{ + +} diff --git a/src/ThemeData_applicators.cpp b/src/ThemeData_applicators.cpp new file mode 100644 index 000000000..83e4d6aa6 --- /dev/null +++ b/src/ThemeData_applicators.cpp @@ -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("pos").cwiseProduct(scale); + image->setPosition(Eigen::Vector3f(denormalized.x(), denormalized.y(), 0)); + } + + if(properties & ThemeFlags::SIZE && elem->has("size")) + image->setResize(elem->get("size").cwiseProduct(scale), true); + + if(properties & ORIGIN && elem->has("origin")) + image->setOrigin(elem->get("origin")); + + if(properties & PATH && elem->has("path")) + image->setImage(elem->get("path")); + + if(properties & TILING && elem->has("tile")) + image->setTiling(elem->get("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("pos").cwiseProduct(scale); + patch->setPosition(Eigen::Vector3f(denormalized.x(), denormalized.y(), 0)); + } + + if(properties & PATH && elem->has("path")) + patch->setImagePath(elem->get("path")); + + if(properties & ThemeFlags::SIZE && elem->has("size")) + patch->setSize(elem->get("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("pos").cwiseProduct(scale); + text->setPosition(Eigen::Vector3f(denormalized.x(), denormalized.y(), 0)); + } + + if(properties & ThemeFlags::SIZE && elem->has("size")) + text->setSize(elem->get("size").cwiseProduct(scale)); + + if(properties & COLOR && elem->has("color")) + text->setColor(elem->get("color")); + + if(properties & CENTER && elem->has("center")) + text->setCentered(elem->get("center")); + + if(properties & TEXT && elem->has("text")) + text->setText(elem->get("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("path"); + auto cacheIt = mSoundCache.find(path); + if(cacheIt != mSoundCache.end()) + { + cacheIt->second->play(); + return; + } + + std::shared_ptr sound = std::shared_ptr(new Sound(path)); + sound->play(); + mSoundCache[path] = sound; + } +} diff --git a/src/components/GuiFastSelect.cpp b/src/components/GuiFastSelect.cpp index 2df321dea..78914057e 100644 --- a/src/components/GuiFastSelect.cpp +++ b/src/components/GuiFastSelect.cpp @@ -12,20 +12,23 @@ GuiFastSelect::GuiFastSelect(Window* window, IGameListView* gamelist) : GuiCompo setSize(Renderer::getScreenWidth() * 0.6f, Renderer::getScreenHeight() * 0.6f); const std::shared_ptr& 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 diff --git a/src/components/GuiMenu.cpp b/src/components/GuiMenu.cpp index da4884a3d..5a48fd57d 100644 --- a/src/components/GuiMenu.cpp +++ b/src/components/GuiMenu.cpp @@ -39,11 +39,12 @@ GuiMenu::GuiMenu(Window* window) : GuiComponent(window), mBackground(window, ":/ addChild(&mBackground); mTheme = std::make_shared(); - 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 >("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); } diff --git a/src/components/ImageComponent.h b/src/components/ImageComponent.h index ce3dd9c3c..725723f61 100644 --- a/src/components/ImageComponent.h +++ b/src/components/ImageComponent.h @@ -23,8 +23,10 @@ public: void setImage(const char* image, size_t length); //Loads image from memory. void setImage(const std::shared_ptr& 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); diff --git a/src/components/TextComponent.cpp b/src/components/TextComponent.cpp index c3ae71ba2..62337433a 100644 --- a/src/components/TextComponent.cpp +++ b/src/components/TextComponent.cpp @@ -138,9 +138,3 @@ std::string TextComponent::getValue() const { return mText; } - -void TextComponent::setFromTheme(const std::shared_ptr& theme, const std::string& fontIdentifier, const std::string& colorIdentifier) -{ - setFont(theme->getFont(fontIdentifier)); - setColor(theme->getColor(colorIdentifier)); -} diff --git a/src/components/TextComponent.h b/src/components/TextComponent.h index 0b73d88e9..1c3410293 100644 --- a/src/components/TextComponent.h +++ b/src/components/TextComponent.h @@ -28,8 +28,6 @@ public: std::shared_ptr getFont() const; - void setFromTheme(const std::shared_ptr& theme, const std::string& fontIdentifier, const std::string& colorIdentifier); - private: void calculateExtent(); diff --git a/src/components/TextListComponent.h b/src/components/TextListComponent.h index 45c709cc7..c89946c71 100644 --- a/src/components/TextListComponent.h +++ b/src/components/TextListComponent.h @@ -13,13 +13,6 @@ #include "../ThemeData.h" #include -#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 class TextListComponent : public GuiComponent @@ -54,16 +47,28 @@ public: void stopScrolling(); inline bool isScrolling() const { return mScrollDir != 0; } - void setTheme(const std::shared_ptr& theme); inline void setCentered(bool centered) { mCentered = centered; } - enum CursorState { + enum CursorState + { CURSOR_STOPPED, CURSOR_SCROLLING }; inline void setCursorChangedCallback(const std::function& func) { mCursorChangedCallback = func; } + inline void setFont(const std::shared_ptr& 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) { 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 mTheme; bool mCentered; std::vector mRowVector; int mCursor; std::function mCursorChangedCallback; + + std::shared_ptr mFont; + unsigned int mSelectorColor; + unsigned int mSelectedColor; + std::shared_ptr mScrollSound; + static const unsigned int COLOR_ID_COUNT = 2; + unsigned int mColors[COLOR_ID_COUNT]; }; template @@ -103,7 +114,11 @@ TextListComponent::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 @@ -116,7 +131,7 @@ void TextListComponent::render(const Eigen::Affine3f& parentTrans) { Eigen::Affine3f trans = parentTrans * getTransform(); - std::shared_ptr font = mTheme->getFont(THEME_FONT); + std::shared_ptr& font = mFont; const int cutoff = 0; const int entrySize = font->getHeight() + 5; @@ -157,7 +172,7 @@ void TextListComponent::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::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(font->buildTextCache(row.name, 0, 0, 0x000000FF)); @@ -273,7 +288,7 @@ void TextListComponent::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::scroll() } onCursorChanged(CURSOR_SCROLLING); - mTheme->playSound("scrollSound"); + + if(mScrollSound) + mScrollSound->play(); } //list management stuff template void TextListComponent::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::onCursorChanged(CursorState state) mCursorChangedCallback(state); } -template -void TextListComponent::setTheme(const std::shared_ptr& theme) -{ - mTheme = theme; - - // invalidate text caches in case font changed - for(auto it = mRowVector.begin(); it != mRowVector.end(); it++) - it->textCache.reset(); -} - #endif diff --git a/src/views/SystemView.cpp b/src/views/SystemView.cpp index e1501d140..a215ad59b 100644 --- a/src/views/SystemView.cpp +++ b/src/views/SystemView.cpp @@ -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) diff --git a/src/views/gamelist/BasicGameListView.cpp b/src/views/gamelist/BasicGameListView.cpp index b1663ff55..7f165c5b7 100644 --- a/src/views/gamelist/BasicGameListView.cpp +++ b/src/views/gamelist/BasicGameListView.cpp @@ -18,7 +18,8 @@ BasicGameListView::BasicGameListView(Window* window, FileData* root) void BasicGameListView::onThemeChanged(const std::shared_ptr& 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) diff --git a/src/views/gamelist/BasicGameListView.h b/src/views/gamelist/BasicGameListView.h index 347934629..d88135e8f 100644 --- a/src/views/gamelist/BasicGameListView.h +++ b/src/views/gamelist/BasicGameListView.h @@ -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& files) override; virtual void launch(FileData* game) override; diff --git a/src/views/gamelist/DetailedGameListView.cpp b/src/views/gamelist/DetailedGameListView.cpp index 06bf730d5..aff14d0bd 100644 --- a/src/views/gamelist/DetailedGameListView.cpp +++ b/src/views/gamelist/DetailedGameListView.cpp @@ -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& 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() diff --git a/src/views/gamelist/DetailedGameListView.h b/src/views/gamelist/DetailedGameListView.h index 7e6405244..a2c08901c 100644 --- a/src/views/gamelist/DetailedGameListView.h +++ b/src/views/gamelist/DetailedGameListView.h @@ -10,6 +10,8 @@ public: virtual void onThemeChanged(const std::shared_ptr& 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; }; diff --git a/src/views/gamelist/GridGameListView.h b/src/views/gamelist/GridGameListView.h index 42db55ede..df9771b83 100644 --- a/src/views/gamelist/GridGameListView.h +++ b/src/views/gamelist/GridGameListView.h @@ -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& files) override; virtual void launch(FileData* game) override; diff --git a/src/views/gamelist/IGameListView.h b/src/views/gamelist/IGameListView.h index 14abab01d..a2ec8b5ab 100644 --- a/src/views/gamelist/IGameListView.h +++ b/src/views/gamelist/IGameListView.h @@ -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 mTheme; diff --git a/src/views/gamelist/ISimpleGameListView.cpp b/src/views/gamelist/ISimpleGameListView.cpp index 1f986644a..fb678d1ee 100644 --- a/src/views/gamelist/ISimpleGameListView.cpp +++ b/src/views/gamelist/ISimpleGameListView.cpp @@ -23,14 +23,10 @@ ISimpleGameListView::ISimpleGameListView(Window* window, FileData* root) : IGame void ISimpleGameListView::onThemeChanged(const std::shared_ptr& 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);