2014-06-25 16:29:58 +00:00
|
|
|
#pragma once
|
2017-10-31 17:12:50 +00:00
|
|
|
#ifndef ES_APP_COMPONENTS_TEXT_LIST_COMPONENT_H
|
|
|
|
#define ES_APP_COMPONENTS_TEXT_LIST_COMPONENT_H
|
2014-06-25 16:29:58 +00:00
|
|
|
|
|
|
|
#include "components/IList.h"
|
|
|
|
#include "Renderer.h"
|
|
|
|
#include "resources/Font.h"
|
|
|
|
#include "InputManager.h"
|
|
|
|
#include "Sound.h"
|
|
|
|
#include "Log.h"
|
|
|
|
#include "ThemeData.h"
|
|
|
|
#include "Util.h"
|
|
|
|
#include <vector>
|
|
|
|
#include <string>
|
|
|
|
#include <memory>
|
|
|
|
#include <functional>
|
|
|
|
|
|
|
|
struct TextListData
|
|
|
|
{
|
|
|
|
unsigned int colorId;
|
|
|
|
std::shared_ptr<TextCache> textCache;
|
|
|
|
};
|
|
|
|
|
|
|
|
//A graphical list. Supports multiple colors for rows and scrolling.
|
|
|
|
template <typename T>
|
|
|
|
class TextListComponent : public IList<TextListData, T>
|
|
|
|
{
|
|
|
|
protected:
|
|
|
|
using IList<TextListData, T>::mEntries;
|
|
|
|
using IList<TextListData, T>::listUpdate;
|
|
|
|
using IList<TextListData, T>::listInput;
|
|
|
|
using IList<TextListData, T>::listRenderTitleOverlay;
|
|
|
|
using IList<TextListData, T>::getTransform;
|
|
|
|
using IList<TextListData, T>::mSize;
|
|
|
|
using IList<TextListData, T>::mCursor;
|
|
|
|
using IList<TextListData, T>::Entry;
|
|
|
|
|
|
|
|
public:
|
|
|
|
using IList<TextListData, T>::size;
|
|
|
|
using IList<TextListData, T>::isScrolling;
|
|
|
|
using IList<TextListData, T>::stopScrolling;
|
|
|
|
|
|
|
|
TextListComponent(Window* window);
|
|
|
|
|
|
|
|
bool input(InputConfig* config, Input input) override;
|
|
|
|
void update(int deltaTime) override;
|
2017-10-28 20:24:35 +00:00
|
|
|
void render(const Transform4x4f& parentTrans) override;
|
2014-06-25 16:29:58 +00:00
|
|
|
void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override;
|
|
|
|
|
|
|
|
void add(const std::string& name, const T& obj, unsigned int colorId);
|
|
|
|
|
|
|
|
enum Alignment
|
|
|
|
{
|
|
|
|
ALIGN_LEFT,
|
|
|
|
ALIGN_CENTER,
|
|
|
|
ALIGN_RIGHT
|
|
|
|
};
|
|
|
|
|
|
|
|
inline void setAlignment(Alignment align) { mAlignment = align; }
|
|
|
|
|
|
|
|
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 = mEntries.begin(); it != mEntries.end(); it++)
|
|
|
|
it->data.textCache.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void setUppercase(bool uppercase)
|
|
|
|
{
|
|
|
|
mUppercase = true;
|
|
|
|
for(auto it = mEntries.begin(); it != mEntries.end(); it++)
|
|
|
|
it->data.textCache.reset();
|
|
|
|
}
|
|
|
|
|
2017-05-29 01:08:07 +00:00
|
|
|
inline void setSelectorHeight(float selectorScale) { mSelectorHeight = selectorScale; }
|
|
|
|
inline void setSelectorOffsetY(float selectorOffsetY) { mSelectorOffsetY = selectorOffsetY; }
|
2014-06-25 16:29:58 +00:00
|
|
|
inline void setSelectorColor(unsigned int color) { mSelectorColor = color; }
|
|
|
|
inline void setSelectedColor(unsigned int color) { mSelectedColor = color; }
|
|
|
|
inline void setColor(unsigned int id, unsigned int color) { mColors[id] = color; }
|
|
|
|
inline void setLineSpacing(float lineSpacing) { mLineSpacing = lineSpacing; }
|
|
|
|
|
|
|
|
protected:
|
2017-09-14 01:18:52 +00:00
|
|
|
virtual void onScroll(int amt) { if(!mScrollSound.empty()) Sound::get(mScrollSound)->play(); }
|
2014-06-25 16:29:58 +00:00
|
|
|
virtual void onCursorChanged(const CursorState& state);
|
|
|
|
|
|
|
|
private:
|
2017-07-20 07:07:02 +00:00
|
|
|
static const int MARQUEE_DELAY = 1000;
|
2014-06-25 16:29:58 +00:00
|
|
|
static const int MARQUEE_SPEED = 8;
|
|
|
|
static const int MARQUEE_RATE = 1;
|
|
|
|
|
|
|
|
int mMarqueeOffset;
|
|
|
|
int mMarqueeTime;
|
|
|
|
|
|
|
|
Alignment mAlignment;
|
|
|
|
float mHorizontalMargin;
|
|
|
|
|
|
|
|
std::function<void(CursorState state)> mCursorChangedCallback;
|
|
|
|
|
|
|
|
std::shared_ptr<Font> mFont;
|
|
|
|
bool mUppercase;
|
|
|
|
float mLineSpacing;
|
2017-05-29 01:08:07 +00:00
|
|
|
float mSelectorHeight;
|
|
|
|
float mSelectorOffsetY;
|
2014-06-25 16:29:58 +00:00
|
|
|
unsigned int mSelectorColor;
|
|
|
|
unsigned int mSelectedColor;
|
2017-09-14 01:18:52 +00:00
|
|
|
std::string mScrollSound;
|
2014-06-25 16:29:58 +00:00
|
|
|
static const unsigned int COLOR_ID_COUNT = 2;
|
|
|
|
unsigned int mColors[COLOR_ID_COUNT];
|
2017-05-29 01:08:07 +00:00
|
|
|
|
|
|
|
ImageComponent mSelectorImage;
|
2014-06-25 16:29:58 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
TextListComponent<T>::TextListComponent(Window* window) :
|
2017-05-29 01:08:07 +00:00
|
|
|
IList<TextListData, T>(window), mSelectorImage(window)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
|
|
|
mMarqueeOffset = 0;
|
|
|
|
mMarqueeTime = -MARQUEE_DELAY;
|
|
|
|
|
|
|
|
mHorizontalMargin = 0;
|
|
|
|
mAlignment = ALIGN_CENTER;
|
|
|
|
|
|
|
|
mFont = Font::get(FONT_SIZE_MEDIUM);
|
|
|
|
mUppercase = false;
|
|
|
|
mLineSpacing = 1.5f;
|
2017-05-29 01:08:07 +00:00
|
|
|
mSelectorHeight = mFont->getSize() * 1.5f;
|
|
|
|
mSelectorOffsetY = 0;
|
2014-06-25 16:29:58 +00:00
|
|
|
mSelectorColor = 0x000000FF;
|
|
|
|
mSelectedColor = 0;
|
|
|
|
mColors[0] = 0x0000FFFF;
|
|
|
|
mColors[1] = 0x00FF00FF;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename T>
|
2017-10-28 20:24:35 +00:00
|
|
|
void TextListComponent<T>::render(const Transform4x4f& parentTrans)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2017-10-28 20:24:35 +00:00
|
|
|
Transform4x4f trans = parentTrans * getTransform();
|
2014-06-25 16:29:58 +00:00
|
|
|
|
|
|
|
std::shared_ptr<Font>& font = mFont;
|
|
|
|
|
|
|
|
if(size() == 0)
|
|
|
|
return;
|
|
|
|
|
2017-06-27 03:34:37 +00:00
|
|
|
const float entrySize = std::max(font->getHeight(1.0), (float)font->getSize()) * mLineSpacing;
|
2014-06-25 16:29:58 +00:00
|
|
|
|
|
|
|
int startEntry = 0;
|
|
|
|
|
|
|
|
//number of entries that can fit on the screen simultaniously
|
|
|
|
int screenCount = (int)(mSize.y() / entrySize + 0.5f);
|
|
|
|
|
|
|
|
if(size() >= screenCount)
|
|
|
|
{
|
|
|
|
startEntry = mCursor - screenCount/2;
|
|
|
|
if(startEntry < 0)
|
|
|
|
startEntry = 0;
|
|
|
|
if(startEntry >= size() - screenCount)
|
|
|
|
startEntry = size() - screenCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
float y = 0;
|
|
|
|
|
|
|
|
int listCutoff = startEntry + screenCount;
|
|
|
|
if(listCutoff > size())
|
|
|
|
listCutoff = size();
|
|
|
|
|
|
|
|
// draw selector bar
|
|
|
|
if(startEntry < listCutoff)
|
|
|
|
{
|
2017-05-29 01:08:07 +00:00
|
|
|
if (mSelectorImage.hasImage()) {
|
|
|
|
mSelectorImage.setPosition(0.f, (mCursor - startEntry)*entrySize + mSelectorOffsetY, 0.f);
|
|
|
|
mSelectorImage.render(trans);
|
|
|
|
} else {
|
|
|
|
Renderer::setMatrix(trans);
|
|
|
|
Renderer::drawRect(0.f, (mCursor - startEntry)*entrySize + mSelectorOffsetY, mSize.x(), mSelectorHeight, mSelectorColor);
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// clip to inside margins
|
2017-10-28 20:24:35 +00:00
|
|
|
Vector3f dim(mSize.x(), mSize.y(), 0);
|
2014-06-25 16:29:58 +00:00
|
|
|
dim = trans * dim - trans.translation();
|
2017-10-28 20:24:35 +00:00
|
|
|
Renderer::pushClipRect(Vector2i((int)(trans.translation().x() + mHorizontalMargin), (int)trans.translation().y()),
|
|
|
|
Vector2i((int)(dim.x() - mHorizontalMargin*2), (int)dim.y()));
|
2014-06-25 16:29:58 +00:00
|
|
|
|
|
|
|
for(int i = startEntry; i < listCutoff; i++)
|
|
|
|
{
|
|
|
|
typename IList<TextListData, T>::Entry& entry = mEntries.at((unsigned int)i);
|
|
|
|
|
|
|
|
unsigned int color;
|
|
|
|
if(mCursor == i && mSelectedColor)
|
|
|
|
color = mSelectedColor;
|
|
|
|
else
|
|
|
|
color = mColors[entry.data.colorId];
|
|
|
|
|
|
|
|
if(!entry.data.textCache)
|
|
|
|
entry.data.textCache = std::unique_ptr<TextCache>(font->buildTextCache(mUppercase ? strToUpper(entry.name) : entry.name, 0, 0, 0x000000FF));
|
|
|
|
|
|
|
|
entry.data.textCache->setColor(color);
|
|
|
|
|
2017-10-28 20:24:35 +00:00
|
|
|
Vector3f offset(0, y, 0);
|
2014-06-25 16:29:58 +00:00
|
|
|
|
|
|
|
switch(mAlignment)
|
|
|
|
{
|
|
|
|
case ALIGN_LEFT:
|
|
|
|
offset[0] = mHorizontalMargin;
|
|
|
|
break;
|
|
|
|
case ALIGN_CENTER:
|
|
|
|
offset[0] = (mSize.x() - entry.data.textCache->metrics.size.x()) / 2;
|
2017-05-29 01:08:07 +00:00
|
|
|
if(offset[0] < mHorizontalMargin)
|
|
|
|
offset[0] = mHorizontalMargin;
|
2014-06-25 16:29:58 +00:00
|
|
|
break;
|
|
|
|
case ALIGN_RIGHT:
|
|
|
|
offset[0] = (mSize.x() - entry.data.textCache->metrics.size.x());
|
|
|
|
offset[0] -= mHorizontalMargin;
|
2017-05-29 01:08:07 +00:00
|
|
|
if(offset[0] < mHorizontalMargin)
|
|
|
|
offset[0] = mHorizontalMargin;
|
2014-06-25 16:29:58 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(mCursor == i)
|
|
|
|
offset[0] -= mMarqueeOffset;
|
|
|
|
|
2017-10-28 20:24:35 +00:00
|
|
|
Transform4x4f drawTrans = trans;
|
2014-06-25 16:29:58 +00:00
|
|
|
drawTrans.translate(offset);
|
|
|
|
Renderer::setMatrix(drawTrans);
|
|
|
|
|
|
|
|
font->renderTextCache(entry.data.textCache.get());
|
|
|
|
|
|
|
|
y += entrySize;
|
|
|
|
}
|
|
|
|
|
|
|
|
Renderer::popClipRect();
|
|
|
|
|
|
|
|
listRenderTitleOverlay(trans);
|
|
|
|
|
|
|
|
GuiComponent::renderChildren(trans);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
bool TextListComponent<T>::input(InputConfig* config, Input input)
|
|
|
|
{
|
|
|
|
if(size() > 0)
|
|
|
|
{
|
|
|
|
if(input.value != 0)
|
|
|
|
{
|
|
|
|
if(config->isMappedTo("down", input))
|
|
|
|
{
|
|
|
|
listInput(1);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(config->isMappedTo("up", input))
|
|
|
|
{
|
|
|
|
listInput(-1);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if(config->isMappedTo("pagedown", input))
|
|
|
|
{
|
|
|
|
listInput(10);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(config->isMappedTo("pageup", input))
|
|
|
|
{
|
|
|
|
listInput(-10);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}else{
|
|
|
|
if(config->isMappedTo("down", input) || config->isMappedTo("up", input) ||
|
|
|
|
config->isMappedTo("pagedown", input) || config->isMappedTo("pageup", input))
|
|
|
|
{
|
|
|
|
stopScrolling();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return GuiComponent::input(config, input);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
void TextListComponent<T>::update(int deltaTime)
|
|
|
|
{
|
|
|
|
listUpdate(deltaTime);
|
|
|
|
if(!isScrolling() && size() > 0)
|
|
|
|
{
|
|
|
|
//if we're not scrolling and this object's text goes outside our size, marquee it!
|
|
|
|
const std::string& text = mEntries.at((unsigned int)mCursor).name;
|
|
|
|
|
2017-10-28 20:24:35 +00:00
|
|
|
Vector2f textSize = mFont->sizeText(text);
|
2014-06-25 16:29:58 +00:00
|
|
|
|
|
|
|
//it's long enough to marquee
|
2017-05-29 01:08:07 +00:00
|
|
|
if(textSize.x() - mMarqueeOffset > mSize.x() - 12 - mHorizontalMargin * 2)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
|
|
|
mMarqueeTime += deltaTime;
|
|
|
|
while(mMarqueeTime > MARQUEE_SPEED)
|
|
|
|
{
|
|
|
|
mMarqueeOffset += MARQUEE_RATE;
|
|
|
|
mMarqueeTime -= MARQUEE_SPEED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
GuiComponent::update(deltaTime);
|
|
|
|
}
|
|
|
|
|
|
|
|
//list management stuff
|
|
|
|
template <typename T>
|
|
|
|
void TextListComponent<T>::add(const std::string& name, const T& obj, unsigned int color)
|
|
|
|
{
|
|
|
|
assert(color < COLOR_ID_COUNT);
|
|
|
|
|
|
|
|
typename IList<TextListData, T>::Entry entry;
|
|
|
|
entry.name = name;
|
|
|
|
entry.object = obj;
|
|
|
|
entry.data.colorId = color;
|
|
|
|
static_cast<IList< TextListData, T >*>(this)->add(entry);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
void TextListComponent<T>::onCursorChanged(const CursorState& state)
|
|
|
|
{
|
|
|
|
mMarqueeOffset = 0;
|
|
|
|
mMarqueeTime = -MARQUEE_DELAY;
|
|
|
|
|
|
|
|
if(mCursorChangedCallback)
|
|
|
|
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");
|
|
|
|
if(!elem)
|
|
|
|
return;
|
|
|
|
|
|
|
|
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));
|
2017-06-27 03:34:37 +00:00
|
|
|
const float selectorHeight = std::max(mFont->getHeight(1.0), (float)mFont->getSize()) * mLineSpacing;
|
|
|
|
setSelectorHeight(selectorHeight);
|
|
|
|
|
2014-06-25 16:29:58 +00:00
|
|
|
if(properties & SOUND && elem->has("scrollSound"))
|
2017-09-14 01:18:52 +00:00
|
|
|
mScrollSound = elem->get<std::string>("scrollSound");
|
2014-06-25 16:29:58 +00:00
|
|
|
|
|
|
|
if(properties & ALIGNMENT)
|
|
|
|
{
|
|
|
|
if(elem->has("alignment"))
|
|
|
|
{
|
|
|
|
const std::string& str = elem->get<std::string>("alignment");
|
|
|
|
if(str == "left")
|
|
|
|
setAlignment(ALIGN_LEFT);
|
|
|
|
else if(str == "center")
|
|
|
|
setAlignment(ALIGN_CENTER);
|
|
|
|
else if(str == "right")
|
|
|
|
setAlignment(ALIGN_RIGHT);
|
|
|
|
else
|
|
|
|
LOG(LogError) << "Unknown TextListComponent alignment \"" << str << "\"!";
|
|
|
|
}
|
|
|
|
if(elem->has("horizontalMargin"))
|
|
|
|
{
|
|
|
|
mHorizontalMargin = elem->get<float>("horizontalMargin") * (this->mParent ? this->mParent->getSize().x() : (float)Renderer::getScreenWidth());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(properties & FORCE_UPPERCASE && elem->has("forceUppercase"))
|
|
|
|
setUppercase(elem->get<bool>("forceUppercase"));
|
|
|
|
|
2017-05-29 01:08:07 +00:00
|
|
|
if(properties & LINE_SPACING)
|
|
|
|
{
|
|
|
|
if(elem->has("lineSpacing"))
|
|
|
|
setLineSpacing(elem->get<float>("lineSpacing"));
|
|
|
|
if(elem->has("selectorHeight"))
|
|
|
|
{
|
|
|
|
setSelectorHeight(elem->get<float>("selectorHeight") * Renderer::getScreenHeight());
|
|
|
|
}
|
|
|
|
if(elem->has("selectorOffsetY"))
|
|
|
|
{
|
|
|
|
float scale = this->mParent ? this->mParent->getSize().y() : (float)Renderer::getScreenHeight();
|
|
|
|
setSelectorOffsetY(elem->get<float>("selectorOffsetY") * scale);
|
|
|
|
} else {
|
|
|
|
setSelectorOffsetY(0.0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (elem->has("selectorImagePath"))
|
|
|
|
{
|
|
|
|
std::string path = elem->get<std::string>("selectorImagePath");
|
|
|
|
bool tile = elem->has("selectorImageTile") && elem->get<bool>("selectorImageTile");
|
|
|
|
mSelectorImage.setImage(path, tile);
|
|
|
|
mSelectorImage.setSize(mSize.x(), mSelectorHeight);
|
|
|
|
mSelectorImage.setColorShift(mSelectorColor);
|
|
|
|
} else {
|
|
|
|
mSelectorImage.setImage("");
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
2017-10-31 17:12:50 +00:00
|
|
|
|
|
|
|
#endif // ES_APP_COMPONENTS_TEXT_LIST_COMPONENT_H
|