#pragma once #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 #include #include #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 { protected: using IList::mEntries; using IList::listUpdate; using IList::listInput; using IList::listRenderTitleOverlay; using IList::getTransform; using IList::mSize; using IList::mCursor; using IList::Entry; public: using IList::size; using IList::isScrolling; using IList::stopScrolling; 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; 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(); } 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; } inline void setLineSpacing(float lineSpacing) { mLineSpacing = lineSpacing; } protected: virtual void onScroll(int amt) { if(mScrollSound) mScrollSound->play(); } virtual void onCursorChanged(const CursorState& state); private: static const int MARQUEE_DELAY = 2000; static const int MARQUEE_SPEED = 8; static const int MARQUEE_RATE = 1; int mMarqueeOffset; int mMarqueeTime; Alignment mAlignment; float mHorizontalMargin; std::function mCursorChangedCallback; std::shared_ptr mFont; bool mUppercase; float mLineSpacing; 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); mUppercase = false; mLineSpacing = 1.5f; 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) return; const float entrySize = round(font->getHeight(mLineSpacing)); 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) { Renderer::setMatrix(trans); Renderer::drawRect(0.f, (mCursor - startEntry)*entrySize + (entrySize - font->getHeight())/2, mSize.x(), font->getHeight(), mSelectorColor); } // clip to inside margins Eigen::Vector3f dim(mSize.x(), mSize.y(), 0); dim = trans * dim - trans.translation(); Renderer::pushClipRect(Eigen::Vector2i((int)(trans.translation().x() + mHorizontalMargin), (int)trans.translation().y()), Eigen::Vector2i((int)(dim.x() - mHorizontalMargin*2), (int)dim.y())); for(int i = startEntry; i < listCutoff; i++) { typename IList::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(mUppercase ? strToUpper(entry.name) : 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 > mSize.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); typename IList::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") * (this->mParent ? this->mParent->getSize().x() : (float)Renderer::getScreenWidth()); } } if(properties & FORCE_UPPERCASE && elem->has("forceUppercase")) setUppercase(elem->get("forceUppercase")); if(properties & LINE_SPACING && elem->has("lineSpacing")) setLineSpacing(elem->get("lineSpacing")); }