#pragma once #include #include #include #include "../GuiComponent.h" #include "ImageComponent.h" #include "../resources/Font.h" enum CursorState { CURSOR_STOPPED, CURSOR_SCROLLING }; struct ScrollTier { int length; // how long we stay on this level before going to the next int scrollDelay; // how long between scrolls }; const int SCROLL_SPEED_COUNT = 3; const ScrollTier SCROLL_SPEED[SCROLL_SPEED_COUNT] = { {500, 500}, {5000, 114}, {0, 8} }; template class IList : public GuiComponent { public: struct Entry { std::string name; UserData object; EntryData data; }; protected: int mCursor; int mScrollTier; int mScrollVelocity; int mScrollTierAccumulator; int mScrollCursorAccumulator; unsigned char mTitleOverlayOpacity; unsigned int mTitleOverlayColor; ImageComponent mGradient; std::shared_ptr mTitleOverlayFont; std::vector mEntries; public: IList(Window* window) : GuiComponent(window), mGradient(window) { mCursor = 0; mScrollTier = 0; mScrollVelocity = 0; mScrollTierAccumulator = 0; mScrollCursorAccumulator = 0; mTitleOverlayOpacity = 0x00; mTitleOverlayColor = 0xFFFFFF00; mGradient.setImage(":/scroll_gradient.png"); mGradient.setColorShift(0x000040FF); mGradient.setOpacity(0); mTitleOverlayFont = Font::get(FONT_SIZE_LARGE); } bool isScrolling() const { return (mScrollVelocity != 0 && mScrollTier > 0); } void stopScrolling() { listInput(0); onCursorChanged(CURSOR_STOPPED); } void clear() { mEntries.clear(); mCursor = 0; listInput(0); onCursorChanged(CURSOR_STOPPED); } inline const std::string& getSelectedName() { assert(size() > 0); return mEntries.at(mCursor).name; } inline const UserData& getSelected() const { assert(size() > 0); return mEntries.at(mCursor).object; } void setCursor(typename std::vector::iterator& it) { assert(it != mEntries.end()); mCursor = it - mEntries.begin(); onCursorChanged(CURSOR_STOPPED); } // returns true if successful (select is in our list), false if not bool setCursor(const UserData& obj) { for(auto it = mEntries.begin(); it != mEntries.end(); it++) { if((*it).object == obj) { mCursor = it - mEntries.begin(); onCursorChanged(CURSOR_STOPPED); return true; } } return false; } // entry management void add(Entry e) { mEntries.push_back(e); } bool remove(const UserData& obj) { for(auto it = mEntries.begin(); it != mEntries.end(); it++) { if((*it).object == obj) { remove(it); return true; } } return false; } inline int size() const { return mEntries.size(); } protected: void remove(typename std::vector::iterator& it) { if(getCursorIndex() > 0 && it - mEntries.begin() <= getCursorIndex()) { setCursorIndex(mCursor - 1); onCursorChanged(CURSOR_STOPPED); } mEntries.erase(it); } void listInput(int velocity) // a velocity of 0 = stop scrolling { mScrollVelocity = velocity; mScrollTier = 0; mScrollTierAccumulator = 0; mScrollCursorAccumulator = 0; scroll(mScrollVelocity); } void listUpdate(int deltaTime) { // update the title overlay opacity const int dir = (mScrollTier >= SCROLL_SPEED_COUNT - 1) ? 1 : -1; // fade in if scroll tier is >= 1, otherwise fade out int op = mTitleOverlayOpacity + deltaTime*dir; // we just do a 1-to-1 time -> opacity, no scaling if(op >= 255) mTitleOverlayOpacity = 255; else if(op <= 0) mTitleOverlayOpacity = 0; else mTitleOverlayOpacity = (unsigned char)op; if(mScrollVelocity == 0 || size() < 2) return; mScrollCursorAccumulator += deltaTime; mScrollTierAccumulator += deltaTime; // we delay scrolling until after scroll tier has updated so isScrolling() returns accurately during onCursorChanged callbacks // we don't just do scroll tier first because it would not catch the scrollDelay == tier length case int scrollCount = 0; while(mScrollCursorAccumulator >= SCROLL_SPEED[mScrollTier].scrollDelay) { mScrollCursorAccumulator -= SCROLL_SPEED[mScrollTier].scrollDelay; scrollCount++; } // are we ready to go even FASTER? while(mScrollTier < SCROLL_SPEED_COUNT - 1 && mScrollTierAccumulator >= SCROLL_SPEED[mScrollTier].length) { mScrollTierAccumulator -= SCROLL_SPEED[mScrollTier].length; mScrollTier++; } // actually perform the scrolling for(int i = 0; i < scrollCount; i++) scroll(mScrollVelocity); } void listRenderTitleOverlay(const Eigen::Affine3f& trans) { if(size() == 0 || !mTitleOverlayFont || mTitleOverlayOpacity == 0) return; // we don't bother caching this because it's only two letters and will change pretty much every frame if we're scrolling const std::string text = getSelectedName().size() >= 2 ? getSelectedName().substr(0, 2) : "??"; Eigen::Vector2f off = mTitleOverlayFont->sizeText(text); off[0] = (mSize.x() - off.x()) * 0.7f; off[1] = (mSize.y() - off.y()) * 0.5f; mGradient.setOpacity(mTitleOverlayOpacity); mGradient.render(trans); mTitleOverlayFont->drawText(text, off, (mTitleOverlayColor & 0xFFFFFF00) | mTitleOverlayOpacity); // relies on mGradient's render to Renderer::setMatrix(trans) } virtual void onSizeChanged() override { mGradient.setResize(mSize); } void scroll(int amt) { if(mScrollVelocity == 0 || size() < 2) return; int cursor = mCursor + amt; int absAmt = amt < 0 ? -amt : amt; // stop at the end if we've been holding down the button for a long time or // we're scrolling faster than one item at a time (e.g. page up/down) // otherwise, loop around if(mScrollTier > 0 || absAmt > 1) { if(cursor < 0) cursor = 0; else if(cursor >= size()) cursor = size() - 1; }else{ if(cursor < 0) cursor += size(); else if(cursor >= size()) cursor -= size(); } if(cursor != mCursor) onScroll(absAmt); mCursor = cursor; onCursorChanged((mScrollTier > 0) ? CURSOR_SCROLLING : CURSOR_STOPPED); } virtual void onCursorChanged(const CursorState& state) {} virtual void onScroll(int amt) {} };