Commit 1aa291eb authored by Aloshi's avatar Aloshi

ImageGridComponent & TextListComponent have had common list functionality

refactored into IList.
parent 7699a4f9
......@@ -244,7 +244,6 @@ set(ES_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentListComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/HelpComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/IList.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/NinePatchComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/RatingComponent.cpp
......
......@@ -62,7 +62,7 @@ bool GuiMenu::input(InputConfig* config, Input input)
Sound::getFromTheme(mTheme, "menu", "menuClose")->play();
delete this;
return true;
}else if(config->isMappedTo("a", input) && mList.getList().size() > 0)
}else if(config->isMappedTo("a", input) && mList.size() > 0)
{
mList.getSelected()();
delete this;
......
#include "IList.h"
const IList::ScrollTier IList::SCROLL_SPEED[IList::SCROLL_SPEED_COUNT] = {
{500, 500},
{2600, 150},
{0, 100}
};
IList::IList()
{
mScrollTier = 0;
mScrollVelocity = 0;
mScrollTierAccumulator = 0;
mScrollCursorAccumulator = 0;
}
void IList::listInput(int velocity)
{
mScrollVelocity = velocity;
mScrollTier = 0;
mScrollTierAccumulator = 0;
mScrollCursorAccumulator = 0;
scroll(mScrollVelocity);
}
void IList::listUpdate(int deltaTime)
{
if(mScrollVelocity == 0 || getLength() < 2)
return;
mScrollCursorAccumulator += deltaTime;
mScrollTierAccumulator += deltaTime;
while(mScrollCursorAccumulator >= SCROLL_SPEED[mScrollTier].scrollDelay)
{
mScrollCursorAccumulator -= SCROLL_SPEED[mScrollTier].scrollDelay;
scroll(mScrollVelocity);
}
// are we ready to go even FASTER?
while(mScrollTier < SCROLL_SPEED_COUNT - 1 && mScrollTierAccumulator >= SCROLL_SPEED[mScrollTier].length)
{
mScrollTierAccumulator -= SCROLL_SPEED[mScrollTier].length;
mScrollTier++;
}
}
void IList::scroll(int amt)
{
if(mScrollVelocity == 0 || getLength() < 2)
return;
int cursor = getCursorIndex() + 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 >= getLength())
cursor = getLength() - 1;
}else{
if(cursor < 0)
cursor += getLength();
else if(cursor >= getLength())
cursor -= getLength();
}
if(cursor != getCursorIndex())
onScroll(absAmt);
setCursorIndex(cursor);
}
bool IList::isScrolling() const
{
return (mScrollVelocity != 0 && mScrollTier > 0);
}
#pragma once
class IList
{
public:
IList();
#include <string>
#include <vector>
bool isScrolling() const;
protected:
void listInput(int velocity); // a velocity of 0 = stop scrolling
void listUpdate(int deltaTime);
enum CursorState
{
CURSOR_STOPPED,
CURSOR_SCROLLING
};
virtual int getCursorIndex() = 0;
virtual void setCursorIndex(int index) = 0; // (index >= 0 && index < getLength()) is guaranteed to be true
virtual int getLength() = 0;
struct ScrollTier
{
int length; // how long we stay on this level before going to the next
int scrollDelay; // how long between scrolls
};
void scroll(int amt);
virtual void onScroll(int amt) {};
const int SCROLL_SPEED_COUNT = 3;
const ScrollTier SCROLL_SPEED[SCROLL_SPEED_COUNT] = {
{500, 500},
{2600, 150},
{0, 100}
};
private:
struct ScrollTier
template <typename EntryData, typename UserData>
class IList
{
public:
struct Entry
{
int length; // how long we stay on this level before going to the next
int scrollDelay; // how long between scrolls
std::string name;
UserData object;
EntryData data;
};
static const int SCROLL_SPEED_COUNT = 3;
static const ScrollTier SCROLL_SPEED[SCROLL_SPEED_COUNT];
protected:
int mCursor;
int mScrollTier;
int mScrollVelocity;
int mScrollTierAccumulator;
int mScrollCursorAccumulator;
std::vector<Entry> mEntries;
public:
IList()
{
mCursor = 0;
mScrollTier = 0;
mScrollVelocity = 0;
mScrollTierAccumulator = 0;
mScrollCursorAccumulator = 0;
}
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<Entry>::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<Entry>::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)
{
if(mScrollVelocity == 0 || size() < 2)
return;
mScrollCursorAccumulator += deltaTime;
mScrollTierAccumulator += deltaTime;
while(mScrollCursorAccumulator >= SCROLL_SPEED[mScrollTier].scrollDelay)
{
mScrollCursorAccumulator -= SCROLL_SPEED[mScrollTier].scrollDelay;
scroll(mScrollVelocity);
}
// are we ready to go even FASTER?
while(mScrollTier < SCROLL_SPEED_COUNT - 1 && mScrollTierAccumulator >= SCROLL_SPEED[mScrollTier].length)
{
mScrollTierAccumulator -= SCROLL_SPEED[mScrollTier].length;
mScrollTier++;
}
}
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) {}
};
......@@ -5,49 +5,25 @@
#include "../components/ImageComponent.h"
#include "../Log.h"
struct ImageGridData
{
std::shared_ptr<TextureResource> texture;
};
template<typename T>
class ImageGridComponent : public GuiComponent, public IList
class ImageGridComponent : public GuiComponent, public IList<ImageGridData, T>
{
public:
ImageGridComponent(Window* window);
struct Entry
{
std::shared_ptr<TextureResource> texture;
T object;
Entry() {}
Entry(std::shared_ptr<TextureResource> t, const T& o) : texture(t), object(o) {}
};
void add(const std::string& imagePath, const T& obj);
void remove(const T& obj);
void clear();
void setCursor(const T& select);
void setCursor(typename std::vector<Entry>::const_iterator& it);
inline const T& getSelected() const { return mEntries.at(mCursor).object; }
inline const std::vector<Entry>& getList() const { return mEntries; }
enum CursorState {
CURSOR_STOPPED,
CURSOR_SCROLLING
};
void stopScrolling();
void add(const std::string& name, const std::string& imagePath, const T& obj);
void onSizeChanged() override;
bool input(InputConfig* config, Input input) override;
void update(int deltaTime) override;
void render(const Eigen::Affine3f& parentTrans) override;
protected:
virtual int getCursorIndex() { return mCursor; }
virtual void setCursorIndex(int index) { mCursor = index; onCursorChanged(CURSOR_STOPPED); }
virtual int getLength() { return mEntries.size(); }
private:
Eigen::Vector2f getSquareSize(std::shared_ptr<TextureResource> tex = nullptr) const
{
......@@ -73,7 +49,7 @@ private:
// calc biggest square size
for(auto it = mEntries.begin(); it != mEntries.end(); it++)
{
Eigen::Vector2f chkSize = getSquareSize(it->texture);
Eigen::Vector2f chkSize = getSquareSize(it->data.texture);
if(chkSize.x() > squareSize.x())
squareSize[0] = chkSize[0];
if(chkSize.y() > squareSize.y())
......@@ -95,13 +71,10 @@ private:
void buildImages();
void updateImages();
void onCursorChanged(CursorState state);
int mCursor;
virtual void onCursorChanged(const CursorState& state);
bool mEntriesDirty;
std::vector<Entry> mEntries;
std::vector<ImageComponent> mImages;
};
......@@ -109,79 +82,19 @@ template<typename T>
ImageGridComponent<T>::ImageGridComponent(Window* window) : GuiComponent(window)
{
mEntriesDirty = true;
mCursor = 0;
}
template<typename T>
void ImageGridComponent<T>::add(const std::string& imagePath, const T& obj)
void ImageGridComponent<T>::add(const std::string& name, const std::string& imagePath, const T& obj)
{
Entry e(ResourceManager::getInstance()->fileExists(imagePath) ? TextureResource::get(imagePath) : TextureResource::get(":/button.png"), obj);
mEntries.push_back(e);
Entry entry;
entry.name = name;
entry.object = obj;
entry.data.texture = ResourceManager::getInstance()->fileExists(imagePath) ? TextureResource::get(imagePath) : TextureResource::get(":/button.png");
static_cast<IList< ImageGridData, T >*>(this)->add(entry);
mEntriesDirty = true;
}
template<typename T>
void ImageGridComponent<T>::remove(const T& obj)
{
for(auto it = mEntries.begin(); it != mEntries.end(); it++)
{
if((*it).object == obj)
{
if(mCursor > 0 && it - mEntries.begin() >= mCursor)
{
mCursor--;
onCursorChanged(CURSOR_STOPPED);
}
mEntriesDirty = true;
mEntries.erase(it);
return;
}
}
LOG(LogError) << "Tried to remove an object we couldn't find";
}
template<typename T>
void ImageGridComponent<T>::clear()
{
mEntries.clear();
mCursor = 0;
onCursorChanged(CURSOR_STOPPED);
mEntriesDirty = true;
}
template<typename T>
void ImageGridComponent<T>::setCursor(const T& obj)
{
for(auto it = mEntries.begin(); it != mEntries.end(); it++)
{
if((*it).object == obj)
{
mCursor = it - mEntries.begin();
onCursorChanged(CURSOR_STOPPED);
return;
}
}
LOG(LogError) << "Tried to set cursor to object we couldn't find";
}
template<typename T>
void ImageGridComponent<T>::setCursor(typename std::vector<Entry>::const_iterator& it)
{
assert(it != mEntries.end());
mCursor = it - mEntries.begin();
onCursorChanged(CURSOR_STOPPED);
}
template<typename T>
void ImageGridComponent<T>::stopScrolling()
{
listInput(0);
onCursorChanged(CURSOR_STOPPED);
}
template<typename T>
bool ImageGridComponent<T>::input(InputConfig* config, Input input)
{
......@@ -239,7 +152,7 @@ void ImageGridComponent<T>::render(const Eigen::Affine3f& parentTrans)
}
template<typename T>
void ImageGridComponent<T>::onCursorChanged(CursorState state)
void ImageGridComponent<T>::onCursorChanged(const CursorState& state)
{
updateImages();
}
......@@ -305,13 +218,13 @@ void ImageGridComponent<T>::updateImages()
for(unsigned int img = 0; img < mImages.size(); img++)
{
ImageComponent& image = mImages.at(img);
if(i >= mEntries.size())
if(i >= (unsigned int)size())
{
image.setImage("");
continue;
}
Eigen::Vector2f squareSize = getSquareSize(mEntries.at(i).texture);
Eigen::Vector2f squareSize = getSquareSize(mEntries.at(i).data.texture);
if(i == mCursor)
{
image.setColorShift(0xFFFFFFFF);
......@@ -321,7 +234,7 @@ void ImageGridComponent<T>::updateImages()
image.setResize(squareSize.x(), squareSize.y());
}
image.setImage(mEntries.at(i).texture);
image.setImage(mEntries.at(i).data.texture);
i++;
}
}
......@@ -13,46 +13,26 @@
#include "../ThemeData.h"
#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 GuiComponent, public IList
class TextListComponent : public GuiComponent, public IList<TextListData, T>
{
public:
TextListComponent(Window* window);
virtual ~TextListComponent();
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
{
std::string name;
T object;
unsigned int colorId;
std::shared_ptr<TextCache> textCache;
};
void add(const std::string& name, const T& obj, unsigned int colorId);
void remove(const T& obj);
void clear();
inline const std::string& getSelectedName() const { return mRowVector.at(mCursor).name; }
inline T getSelected() const { return mRowVector.at(mCursor).object; }
inline const std::vector<ListRow>& getList() const { return mRowVector; }
void setCursor(const T& select);
void setCursor(typename std::vector<ListRow>::const_iterator& it);
void stopScrolling();
enum CursorState
{
CURSOR_STOPPED,
CURSOR_SCROLLING
};
enum Alignment
{
ALIGN_LEFT,
......@@ -67,8 +47,8 @@ public:
inline void setFont(const std::shared_ptr<Font>& font)
{
mFont = font;
for(auto it = mRowVector.begin(); it != mRowVector.end(); it++)
it->textCache.reset();
for(auto it = mEntries.begin(); it != mEntries.end(); it++)
it->data.textCache.reset();
}
inline void setSelectorColor(unsigned int color) { mSelectorColor = color; }
......@@ -78,28 +58,20 @@ public:
inline void setSound(const std::shared_ptr<Sound>& sound) { mScrollSound = sound; }
protected:
// IList implementations
virtual int getCursorIndex() { return mCursor; }
virtual void setCursorIndex(int index) { mCursor = index; onCursorChanged(isScrolling() ? CURSOR_SCROLLING : CURSOR_STOPPED); }
virtual int getLength() { return mRowVector.size(); }
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;
void onCursorChanged(CursorState state);
int mMarqueeOffset;
int mMarqueeTime;
Alignment mAlignment;
float mHorizontalMargin;
std::vector<ListRow> mRowVector;
int mCursor;
std::function<void(CursorState state)> mCursorChangedCallback;
std::shared_ptr<Font> mFont;
......@@ -114,8 +86,6 @@ template <typename T>
TextListComponent<T>::TextListComponent(Window* window) :
GuiComponent(window)
{
mCursor = 0;
mMarqueeOffset = 0;
mMarqueeTime = -MARQUEE_DELAY;
......@@ -129,11 +99,6 @@ TextListComponent<T>::TextListComponent(Window* window) :
mColors[1] = 0x00FF00FF;
}
template <typename T>
TextListComponent<T>::~TextListComponent()
{
}
template <typename T>
void TextListComponent<T>::render(const Eigen::Affine3f& parentTrans)
{
......@@ -141,6 +106,13 @@ void TextListComponent<T>::render(const Eigen::Affine3f& parentTrans)
std::shared_ptr<Font>& 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;
......@@ -149,26 +121,20 @@ void TextListComponent<T>::render(const Eigen::Affine3f& parentTrans)
//number of entries that can fit on the screen simultaniously
int screenCount = (int)mSize.y() / entrySize;
if((int)mRowVector.size() >= screenCount)
if(size() >= screenCount)
{
startEntry = mCursor - (int)(screenCount * 0.5);
if(startEntry < 0)
startEntry = 0;
if(startEntry >= (int)mRowVector.size() - screenCount)
startEntry = mRowVector.size() - screenCount;
if(startEntry >= size() - screenCount)
startEntry = size() - screenCount;
}
float y = (float)cutoff;
if(mRowVector.size() == 0)
{
font->drawCenteredText("The list is empty.", 0, y, 0xFF0000FF);
return;
}
int listCutoff = startEntry + screenCount;
if(listCutoff > (int)mRowVector.size())
listCutoff = mRowVector.size();
if(listCutoff > size())
listCutoff = size();
Eigen::Vector3f dim(getSize().x(), getSize().y(), 0);
dim = trans * dim - trans.translation();
......@@ -183,18 +149,18 @@ void TextListComponent<T>::render(const Eigen::Affine3f& parentTrans)
Renderer::drawRect(0, (int)y, (int)getSize().x(), font->getHeight(), mSelectorColor);
}
ListRow& row = mRowVector.at((unsigned int)i);
Entry& entry = mEntries.at((unsigned int)i);
unsigned int color;
if(mCursor == i && mSelectedColor)
color = mSelectedColor;
else
color = mColors[row.colorId];
color = mColors[entry.data.colorId];
if(!row.textCache)
row.textCache = std::unique_ptr<TextCache>(font->buildTextCache(row.name, 0, 0, 0x000000FF));
if(!entry.data.textCache)
entry.data.textCache = std::unique_ptr<TextCache>(font->buildTextCache(entry.name, 0, 0, 0x000000FF));
row.textCache->setColor(color);
entry.data.textCache->setColor(color);
Eigen::Vector3f offset(0, y, 0);
......@@ -204,12 +170,12 @@ void TextListComponent<T>::render(const Eigen::Affine3f& parentTrans)
offset[0] = mHorizontalMargin;
break;
case ALIGN_CENTER:
offset[0] = (mSize.x() - row.textCache->metrics.size.x()) / 2;
offset[0] = (mSize.x() - entry.data.textCache->metrics.size.x()) / 2;
if(offset[0] < 0)
offset[0] = 0;
break;
case ALIGN_RIGHT: