Theme applicators have become the virtual method

GuiComponent::applyTheme(theme, view, element, properties).
Applying fonts works now.
This commit is contained in:
Aloshi 2013-12-31 23:39:22 -06:00
parent 8bc33ce309
commit e6d0da998b
21 changed files with 230 additions and 178 deletions

View file

@ -233,7 +233,6 @@ 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

@ -3,6 +3,7 @@
#include "Log.h"
#include "Renderer.h"
#include "animations/AnimationController.h"
#include "ThemeData.h"
GuiComponent::GuiComponent(Window* window) : mWindow(window), mParent(NULL), mOpacity(255),
mPosition(Eigen::Vector3f::Zero()), mSize(Eigen::Vector2f::Zero()), mTransform(Eigen::Affine3f::Identity())
@ -225,3 +226,20 @@ void GuiComponent::stopAnimation(unsigned char slot)
mAnimationMap[slot] = NULL;
}
}
void GuiComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties)
{
Eigen::Vector2f scale = getParent() ? getParent()->getSize() : Eigen::Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "");
using namespace ThemeFlags;
if(properties & POSITION && elem->has("pos"))
{
Eigen::Vector2f denormalized = elem->get<Eigen::Vector2f>("pos").cwiseProduct(scale);
setPosition(Eigen::Vector3f(denormalized.x(), denormalized.y(), 0));
}
if(properties & ThemeFlags::SIZE && elem->has("size"))
setSize(elem->get<Eigen::Vector2f>("size").cwiseProduct(scale));
}

View file

@ -8,6 +8,7 @@
class Window;
class Animation;
class AnimationController;
class ThemeData;
class GuiComponent
{
@ -66,6 +67,10 @@ public:
virtual void onFocusGained() {};
virtual void onFocusLost() {};
// Default implementation just handles <pos> and <size> tags as normalized float pairs.
// You probably want to keep this behavior for any derived classes as well as add your own.
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties);
protected:
void renderChildren(const Eigen::Affine3f& transform) const;

View file

@ -209,6 +209,7 @@ void ThemeData::parseElement(const pugi::xml_node& root, const std::map<std::str
ThemeException error;
error.setFiles(mPaths);
element.type = root.name();
element.extra = root.attribute("extra").as_bool(false);
for(pugi::xml_node node = root.first_child(); node; node = node.next_sibling())
@ -273,3 +274,50 @@ ThemeData::ThemeView::~ThemeView()
for(auto it = mExtras.begin(); it != mExtras.end(); it++)
delete *it;
}
const ThemeData::ThemeElement* ThemeData::getElement(const std::string& view, const std::string& element, const std::string& expectedType) const
{
auto viewIt = mViews.find(view);
if(viewIt == mViews.end())
{
// also check common if the view never existed to begin with
viewIt = mViews.find("common");
if(viewIt == mViews.end())
return NULL;
}
auto elemIt = viewIt->second.elements.find(element);
if(elemIt == viewIt->second.elements.end()) return NULL;
if(elemIt->second.type != expectedType && !expectedType.empty())
{
LOG(LogWarning) << " requested mismatched theme type for [" << view << "." << element << "] - expected \""
<< expectedType << "\", got \"" << elemIt->second.type << "\"";
return NULL;
}
return &elemIt->second;
}
void ThemeData::playSound(const std::string& elementName)
{
const ThemeElement* elem = getElement("common", elementName, "sound");
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

@ -69,7 +69,7 @@ ThemeException& operator<<(ThemeException& e, T appendMsg)
class ThemeData
{
private:
public:
class ThemeElement
{
@ -80,11 +80,12 @@ private:
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)); }
T get(const std::string& prop) const { return boost::get<T>(properties.at(prop)); }
inline bool has(const std::string& prop) { return (properties.find(prop) != properties.end()); }
inline bool has(const std::string& prop) const { return (properties.find(prop) != properties.end()); }
};
private:
class ThemeView
{
private:
@ -119,17 +120,10 @@ public:
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:
void applyPosAndSize(ThemeElement* elem, GuiComponent* comp, unsigned int properties);
// If expectedType is an empty string, will do no type checking.
const ThemeElement* getElement(const std::string& view, const std::string& element, const std::string& expectedType) const;
private:
static std::map< std::string, std::map<std::string, ElementPropertyType> > sElementMap;
@ -142,16 +136,24 @@ private:
void parseView(const pugi::xml_node& viewNode, ThemeView& view);
void parseElement(const pugi::xml_node& elementNode, const std::map<std::string, ElementPropertyType>& typeMap, ThemeElement& element);
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)
{
}
// okay ideas for applying themes to GuiComponents:
// 1. ThemeData::applyToImage(component, args)
// NO, BECAUSE:
// - for templated types (TextListComponent) have to include the whole template in a header
// - inconsistent definitions if mixing templated types (some in a .cpp, some in a .h/.inl)
// 2. template<typename T> ThemeData::apply(component, args) with specialized templates
// NO, BECAUSE:
// - doesn't solve the first drawback
// - can't customize arguments for specific types
// 3. GuiComponent::applyTheme(theme, args) - WINNER
// NO, BECAUSE:
// - can't access private members of ThemeData
// - can't this be solved with enough getters?
// - theme->hasElement and theme->getProperty will require 2x as many map lookups (4 vs 2)
// - why not just return a const ThemeElement...

View file

@ -1,146 +0,0 @@
#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());
}
void ThemeData::applyPosAndSize(ThemeElement* elem, GuiComponent* comp, unsigned int properties)
{
Eigen::Vector2f scale = getScale(comp);
if(properties & POSITION && elem->has("pos"))
{
Eigen::Vector2f denormalized = elem->get<Eigen::Vector2f>("pos").cwiseProduct(scale);
comp->setPosition(Eigen::Vector3f(denormalized.x(), denormalized.y(), 0));
}
if(properties & ThemeFlags::SIZE && elem->has("size"))
comp->setSize(elem->get<Eigen::Vector2f>("size").cwiseProduct(scale));
}
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));
}
applyPosAndSize(elem, patch, properties);
}
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

@ -14,20 +14,20 @@ GuiFastSelect::GuiFastSelect(Window* window, IGameListView* gamelist) : GuiCompo
const std::shared_ptr<ThemeData>& theme = mGameList->getTheme();
using namespace ThemeFlags;
theme->applyToNinePatch("fastSelect", "background", &mBackground, PATH);
mBackground.applyTheme(theme, "fastSelect", "background", PATH);
mBackground.fitTo(mSize);
addChild(&mBackground);
mLetterText.setSize(mSize.x(), mSize.y() * 0.75f);
mLetterText.setCentered(true);
theme->applyToText("fastSelect", "letter", &mLetterText, FONT_PATH | COLOR);
mLetterText.applyTheme(theme, "fastSelect", "letter", 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);
theme->applyToText("fastSelect", "subtext", &mSortText, FONT_PATH | COLOR);
mSortText.applyTheme(theme, "fastSelect", "subtext", FONT_PATH | COLOR);
// TODO - set font size
addChild(&mSortText);

View file

@ -40,7 +40,7 @@ GuiMenu::GuiMenu(Window* window) : GuiComponent(window), mBackground(window, ":/
mTheme = std::make_shared<ThemeData>();
using namespace ThemeFlags;
mTheme->applyToTextList< std::function<void()> >("common", "menu", &mList, FONT_PATH | COLOR);
mList.applyTheme(mTheme, "common", "menu", FONT_PATH | COLOR);
mList.setSelectorColor(0xBBBBBBFF);
mList.setColor(0, 0x0000FFFF);
mList.setColor(1, 0xFF0000FF);

View file

@ -4,6 +4,7 @@
#include <math.h>
#include "../Log.h"
#include "../Renderer.h"
#include "../ThemeData.h"
Eigen::Vector2i ImageComponent::getTextureSize() const
{
@ -243,3 +244,37 @@ void ImageComponent::copyScreen()
resize();
}
void ImageComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties)
{
LOG(LogInfo) << " req image [" << view << "." << element << "] (flags: " << properties << ")";
using namespace ThemeFlags;
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "image");
if(!elem)
{
LOG(LogInfo) << " (missing)";
return;
}
Eigen::Vector2f scale = getParent() ? getParent()->getSize() : Eigen::Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
if(properties & POSITION && elem->has("pos"))
{
Eigen::Vector2f denormalized = elem->get<Eigen::Vector2f>("pos").cwiseProduct(scale);
setPosition(Eigen::Vector3f(denormalized.x(), denormalized.y(), 0));
}
if(properties & ThemeFlags::SIZE && elem->has("size"))
setResize(elem->get<Eigen::Vector2f>("size").cwiseProduct(scale), true);
if(properties & ORIGIN && elem->has("origin"))
setOrigin(elem->get<Eigen::Vector2f>("origin"));
if(properties & PATH && elem->has("path"))
setImage(elem->get<std::string>("path"));
if(properties & TILING && elem->has("tile"))
setTiling(elem->get<bool>("tile"));
}

View file

@ -41,6 +41,8 @@ public:
void render(const Eigen::Affine3f& parentTrans) override;
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override;
private:
Eigen::Vector2f mTargetSize;
Eigen::Vector2f mOrigin;

View file

@ -2,6 +2,7 @@
#include "../Window.h"
#include "../Log.h"
#include "../Renderer.h"
#include "../ThemeData.h"
NinePatchComponent::NinePatchComponent(Window* window, const std::string& path, unsigned int edgeColor, unsigned int centerColor) : GuiComponent(window),
mEdgeColor(edgeColor), mCenterColor(centerColor),
@ -193,3 +194,17 @@ void NinePatchComponent::setCenterColor(unsigned int centerColor)
mCenterColor = centerColor;
updateColors();
}
void NinePatchComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties)
{
GuiComponent::applyTheme(theme, view, element, properties);
using namespace ThemeFlags;
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "ninepatch");
if(!elem)
return;
if(properties & PATH && elem->has("path"))
setImagePath(elem->get<std::string>("path"));
}

View file

@ -18,6 +18,8 @@ public:
void setEdgeColor(unsigned int edgeColor);
void setCenterColor(unsigned int centerColor);
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override;
private:
Eigen::Vector2f getCornerSize() const;

View file

@ -138,3 +138,25 @@ std::string TextComponent::getValue() const
{
return mText;
}
void TextComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties)
{
GuiComponent::applyTheme(theme, view, element, properties);
using namespace ThemeFlags;
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "text");
if(!elem)
return;
if(properties & COLOR && elem->has("color"))
setColor(elem->get<unsigned int>("color"));
if(properties & CENTER && elem->has("center"))
setCentered(elem->get<bool>("center"));
if(properties & TEXT && elem->has("text"))
setText(elem->get<std::string>("text"));
setFont(Font::getFromTheme(elem, properties, mFont));
}

View file

@ -28,6 +28,8 @@ public:
std::shared_ptr<Font> getFont() const;
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override;
private:
void calculateExtent();

View file

@ -24,6 +24,7 @@ public:
bool input(InputConfig* config, Input input) override;
void update(int deltaTime) override;
void render(const Eigen::Affine3f& parentTrans) override;
void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override;
struct ListRow
{
@ -405,4 +406,27 @@ void TextListComponent<T>::onCursorChanged(CursorState state)
mCursorChangedCallback(state);
}
template <typename T>
void TextListComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties)
{
GuiComponent::applyTheme(theme, view, element, properties);
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "textlist");
using namespace ThemeFlags;
if(properties & COLOR)
{
if(elem->has("selectorColor"))
setSelectorColor(elem->get<unsigned int>("selectorColor"));
if(elem->has("selectedColor"))
setSelectedColor(elem->get<unsigned int>("selectedColor"));
if(elem->has("primaryColor"))
setColor(0, elem->get<unsigned int>("primaryColor"));
if(elem->has("secondaryColor"))
setColor(1, elem->get<unsigned int>("secondaryColor"));
}
setFont(Font::getFromTheme(elem, properties, mFont));
}
#endif

View file

@ -553,3 +553,22 @@ void TextCache::setColor(unsigned int color)
{
Renderer::buildGLColorArray(const_cast<GLubyte*>(colors), color, vertCount);
}
std::shared_ptr<Font> Font::getFromTheme(const ThemeData::ThemeElement* elem, unsigned int properties, const std::shared_ptr<Font>& orig)
{
using namespace ThemeFlags;
if(!(properties & FONT_PATH) && !(properties & FONT_SIZE))
return orig;
std::shared_ptr<Font> font;
int size = (orig ? orig->mSize : FONT_SIZE_MEDIUM);
std::string path = (orig ? orig->mPath : getDefaultPath());
float sh = (float)Renderer::getScreenHeight();
if(properties & FONT_SIZE && elem->has("fontSize"))
size = (int)(sh * elem->get<float>("fontSize"));
if(properties & FONT_PATH && elem->has("fontPath"))
path = elem->get<std::string>("fontPath");
return get(size, path);
}

View file

@ -8,6 +8,7 @@
#include FT_FREETYPE_H
#include <Eigen/Dense>
#include "ResourceManager.h"
#include "../ThemeData.h"
class TextCache;
@ -69,6 +70,9 @@ public:
int getSize() const;
static std::string getDefaultPath();
static std::shared_ptr<Font> getFromTheme(const ThemeData::ThemeElement* elem, unsigned int properties, const std::shared_ptr<Font>& orig);
private:
static int getDpiX();
static int getDpiY();

View file

@ -38,7 +38,7 @@ void SystemView::updateData()
using namespace ThemeFlags;
mHeaderImage.setImage("");
mSystem->getTheme()->applyToImage("common", "header", &mHeaderImage, PATH);
mHeaderImage.applyTheme(mSystem->getTheme(), "system", "header", PATH);
// header
if(mHeaderImage.hasImage())
@ -51,7 +51,7 @@ void SystemView::updateData()
mHeaderText.setText(mSystem->getFullName());
}
mSystem->getTheme()->applyToImage("common", "system", &mImage, PATH);
mImage.applyTheme(mSystem->getTheme(), "system", "system", PATH);
}
bool SystemView::input(InputConfig* config, Input input)

View file

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

View file

@ -26,6 +26,7 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) :
mDescContainer.setAutoScroll((int)(1600 + mDescContainer.getSize().x()), 0.025f);
addChild(&mDescContainer);
mDescription.setFont(Font::get(FONT_SIZE_SMALL));
mDescription.setSize(mDescContainer.getSize().x(), 0);
mDescContainer.addChild(&mDescription);
@ -41,7 +42,7 @@ void DetailedGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& them
mHeaderImage.setResize(0, mSize.y() * 0.185f, true);
using namespace ThemeFlags;
theme->applyToText("detailed", "description", &mDescription, POSITION | FONT_PATH | FONT_SIZE);
mDescription.applyTheme(theme, getName(), "description", POSITION | FONT_PATH | FONT_SIZE | COLOR);
}
void DetailedGameListView::updateInfoPanel()

View file

@ -24,8 +24,8 @@ ISimpleGameListView::ISimpleGameListView(Window* window, FileData* root) : IGame
void ISimpleGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
{
using namespace ThemeFlags;
theme->applyToImage(getName(), "background", &mBackground, PATH | TILING);
theme->applyToImage(getName(), "header", &mHeaderImage, PATH);
mBackground.applyTheme(theme, getName(), "background", PATH | TILING);
mHeaderImage.applyTheme(theme, getName(), "header", PATH);
if(mHeaderImage.hasImage())
{