#pragma once #include "IList.h" #include "../Renderer.h" #include "../resources/Font.h" #include "../InputManager.h" #include #include #include #include "../Sound.h" #include "../Log.h" #include "../ThemeData.h" #include struct TextListData { unsigned int colorId; std::shared_ptr textCache; }; //A graphical list. Supports multiple colors for rows and scrolling. template class TextListComponent : public IList { public: TextListComponent(Window* window); 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& 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& func) { mCursorChangedCallback = func; } inline void setFont(const std::shared_ptr& font) { mFont = font; mTitleOverlayFont = Font::get(FONT_SIZE_LARGE, mFont->getPath()); for(auto it = mEntries.begin(); it != mEntries.end(); it++) it->data.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; } inline void setSound(const std::shared_ptr& sound) { mScrollSound = sound; } protected: virtual void onScroll(int amt) { if(mScrollSound) mScrollSound->play(); } virtual void onCursorChanged(const CursorState& state); private: static const int MARQUEE_DELAY = 900; static const int MARQUEE_SPEED = 16; static const int MARQUEE_RATE = 3; int mMarqueeOffset; int mMarqueeTime; Alignment mAlignment; float mHorizontalMargin; 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 TextListComponent::TextListComponent(Window* window) : IList(window) { mMarqueeOffset = 0; mMarqueeTime = -MARQUEE_DELAY; mHorizontalMargin = 0; mAlignment = ALIGN_CENTER; mFont = Font::get(FONT_SIZE_MEDIUM); mSelectorColor = 0x000000FF; mSelectedColor = 0; mColors[0] = 0x0000FFFF; mColors[1] = 0x00FF00FF; } template void TextListComponent::render(const Eigen::Affine3f& parentTrans) { Eigen::Affine3f trans = parentTrans * getTransform(); std::shared_ptr& font = mFont; if(size() == 0) { Renderer::setMatrix(trans); font->drawText("The list is empty.", Eigen::Vector2f(0, 0), 0xFF0000FF); return; } const int cutoff = 0; const int entrySize = font->getHeight() + 5; 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 - (int)(screenCount * 0.5); if(startEntry < 0) startEntry = 0; if(startEntry >= size() - screenCount) startEntry = size() - screenCount; } float y = (float)cutoff; int listCutoff = startEntry + screenCount; if(listCutoff > size()) listCutoff = size(); Eigen::Vector3f dim(getSize().x(), getSize().y(), 0); dim = trans * dim - trans.translation(); Renderer::pushClipRect(Eigen::Vector2i((int)trans.translation().x(), (int)trans.translation().y()), Eigen::Vector2i((int)dim.x(), (int)dim.y())); for(int i = startEntry; i < listCutoff; i++) { //draw selector bar if(mCursor == i) { Renderer::setMatrix(trans); Renderer::drawRect(0, (int)y, (int)getSize().x(), font->getHeight(), mSelectorColor); } 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(font->buildTextCache(entry.name, 0, 0, 0x000000FF)); entry.data.textCache->setColor(color); Eigen::Vector3f offset(0, y, 0); switch(mAlignment) { case ALIGN_LEFT: offset[0] = mHorizontalMargin; break; case ALIGN_CENTER: offset[0] = (mSize.x() - entry.data.textCache->metrics.size.x()) / 2; if(offset[0] < 0) offset[0] = 0; break; case ALIGN_RIGHT: offset[0] = (mSize.x() - entry.data.textCache->metrics.size.x()); offset[0] -= mHorizontalMargin; if(offset[0] < 0) offset[0] = 0; break; } if(mCursor == i) offset[0] -= mMarqueeOffset; Eigen::Affine3f drawTrans = trans; drawTrans.translate(offset); Renderer::setMatrix(drawTrans); font->renderTextCache(entry.data.textCache.get()); y += entrySize; } Renderer::popClipRect(); listRenderTitleOverlay(trans); GuiComponent::renderChildren(trans); } template bool TextListComponent::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 void TextListComponent::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; Eigen::Vector2f textSize = mFont->sizeText(text); //it's long enough to marquee if(textSize.x() - mMarqueeOffset > getSize().x() - 12 - (mAlignment != ALIGN_CENTER ? mHorizontalMargin : 0)) { mMarqueeTime += deltaTime; while(mMarqueeTime > MARQUEE_SPEED) { mMarqueeOffset += MARQUEE_RATE; mMarqueeTime -= MARQUEE_SPEED; } } } GuiComponent::update(deltaTime); } //list management stuff template void TextListComponent::add(const std::string& name, const T& obj, unsigned int color) { assert(color < COLOR_ID_COUNT); Entry entry; entry.name = name; entry.object = obj; entry.data.colorId = color; static_cast*>(this)->add(entry); } template void TextListComponent::onCursorChanged(const CursorState& state) { mMarqueeOffset = 0; mMarqueeTime = -MARQUEE_DELAY; if(mCursorChangedCallback) mCursorChangedCallback(state); } template void TextListComponent::applyTheme(const std::shared_ptr& 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("selectorColor")); if(elem->has("selectedColor")) setSelectedColor(elem->get("selectedColor")); if(elem->has("primaryColor")) setColor(0, elem->get("primaryColor")); if(elem->has("secondaryColor")) setColor(1, elem->get("secondaryColor")); } setFont(Font::getFromTheme(elem, properties, mFont)); if(properties & SOUND && elem->has("scrollSound")) setSound(Sound::get(elem->get("scrollSound"))); if(properties & ALIGNMENT) { if(elem->has("alignment")) { const std::string& str = elem->get("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("horizontalMargin") * (mParent ? mParent->getSize().x() : (float)Renderer::getScreenWidth()); } } }