Grid updates. Animate scrolling option, autoLayout option, center selection option, scroll loop option, image source option

This commit is contained in:
John Rassa 2019-07-06 10:50:50 -04:00
parent cb0db38f54
commit 961571655b
12 changed files with 651 additions and 193 deletions

View file

@ -538,6 +538,7 @@ Reference
## Types of properties:
* NORMALIZED_PAIR - two decimals, in the range [0..1], delimited by a space. For example, `0.25 0.5`. Most commonly used for position (x and y coordinates) and size (width and height).
* NORMALIZED_RECT - four decimals, in the range [0..1], delimited by a space. For example, `0.25 0.5 0.10 0.30`. Most commonly used for padding to store top, left, bottom and right coordinates.
* PATH - a path. If the first character is a `~`, it will be expanded into the environment variable for the home path (`$HOME` for Linux or `%HOMEPATH%` for Windows). If the first character is a `.`, it will be expanded to the theme file's directory, allowing you to specify resources relative to the theme file, like so: `./../general_art/myfont.ttf`.
* BOOLEAN - `true`/`1` or `false`/`0`.
* COLOR - a hexidecimal RGB or RGBA color (6 or 8 digits). If 6 digits, will assume the alpha channel is `FF` (not transparent).
@ -586,13 +587,29 @@ Can be created as an extra.
* `pos` - type: NORMALIZED_PAIR.
* `size` - type: NORMALIZED_PAIR.
- The size of the grid. Take care the selected tile can go out of the grid size, so don't position the grid too close to another element or the screen border.
* `margin` - type: NORMALIZED_PAIR.
* `margin` - type: NORMALIZED_PAIR. Margin between tiles.
* `padding` - type: NORMALIZED_RECT.
- NEW : Padding for displaying tiles.
* `autoLayout` - type: NORMALIZED_PAIR.
- NEW : Number of column and rows in the grid (integer values).
* `autoLayoutSelectedZoom` - type: FLOAT.
- NEW : Zoom factor to apply when a tile is selected.
* `gameImage` - type: PATH.
- The default image used for games which doesn't have an image.
* `folderImage` - type: PATH.
- The default image used for folders which doesn't have an image.
* `imageSource` - type: STRING.
- Selects the image to display. `thumbnail` by default, can also be set to `image` or `marquee`.
* `scrollDirection` - type: STRING.
- `vertical` by default, can also be set to `horizontal`. Not that in `horizontal` mod, the tiles are ordered from top to bottom, then from left to right.
* `centerSelection` - type: BOOLEAN.
- `false` by default, when `true` the selected tile will be locked to the center of the grid.
* `scrollLoop` - type: BOOLEAN.
- `false` by default, when `true` the grid will seamlessly loop around when scrolling reaches the end of the list. Only works when `centerSelection` is `true`.
* `animate` - type : BOOLEAN.
- `true` by default, when `false` the grid scrolling will not be animated.
* `zIndex` - type: FLOAT.
- z-index value for component. Components will be rendered in order of z-index value from low to high.
#### gridtile

View file

@ -144,7 +144,7 @@ void CollectionSystemManager::loadCollectionSystems()
void CollectionSystemManager::loadEnabledListFromSettings()
{
// we parse the auto collection settings list
std::vector<std::string> autoSelected = Utils::String::commaStringToVector(Settings::getInstance()->getString("CollectionSystemsAuto"));
std::vector<std::string> autoSelected = Utils::String::commaStringToVector(Settings::getInstance()->getString("CollectionSystemsAuto"), true);
// iterate the map
for(std::map<std::string, CollectionSystemData>::iterator it = mAutoCollectionSystemsData.begin() ; it != mAutoCollectionSystemsData.end() ; it++ )
@ -153,7 +153,7 @@ void CollectionSystemManager::loadEnabledListFromSettings()
}
// we parse the custom collection settings list
std::vector<std::string> customSelected = Utils::String::commaStringToVector(Settings::getInstance()->getString("CollectionSystemsCustom"));
std::vector<std::string> customSelected = Utils::String::commaStringToVector(Settings::getInstance()->getString("CollectionSystemsCustom"), true);
// iterate the map
for(std::map<std::string, CollectionSystemData>::iterator it = mCustomCollectionSystemsData.begin() ; it != mCustomCollectionSystemsData.end() ; it++ )

View file

@ -110,6 +110,18 @@ bool GridGameListView::input(InputConfig* config, Input input)
return ISimpleGameListView::input(config, input);
}
const std::string GridGameListView::getImagePath(FileData* file)
{
ImageSource src = mGrid.getImageSource();
if (src == ImageSource::IMAGE)
return file->getImagePath();
else if (src == ImageSource::MARQUEE)
return file->getMarqueePath();
return file->getThumbnailPath();
}
void GridGameListView::populateList(const std::vector<FileData*>& files)
{
mGrid.clear();
@ -118,7 +130,7 @@ void GridGameListView::populateList(const std::vector<FileData*>& files)
{
for (auto it = files.cbegin(); it != files.cend(); it++)
{
mGrid.add((*it)->getName(), (*it)->getThumbnailPath(), *it);
mGrid.add((*it)->getName(), getImagePath(*it), *it);
}
}
else

View file

@ -36,6 +36,7 @@ protected:
private:
void updateInfoPanel();
const std::string getImagePath(FileData* file);
void initMDLabels();
void initMDValues();

View file

@ -3,6 +3,7 @@
#include "components/ImageComponent.h"
#include "components/TextComponent.h"
#include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
#include "Log.h"
#include "platform.h"
#include "Settings.h"
@ -18,7 +19,7 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>> The
{ "size", NORMALIZED_PAIR },
{ "maxSize", NORMALIZED_PAIR },
{ "origin", NORMALIZED_PAIR },
{ "rotation", FLOAT },
{ "rotation", FLOAT },
{ "rotationOrigin", NORMALIZED_PAIR },
{ "path", PATH },
{ "default", PATH },
@ -32,9 +33,16 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>> The
{ "pos", NORMALIZED_PAIR },
{ "size", NORMALIZED_PAIR },
{ "margin", NORMALIZED_PAIR },
{ "padding", NORMALIZED_RECT },
{ "autoLayout", NORMALIZED_PAIR },
{ "autoLayoutSelectedZoom", FLOAT },
{ "gameImage", PATH },
{ "folderImage", PATH },
{ "scrollDirection", STRING } } },
{ "imageSource", STRING },
{ "scrollDirection", STRING },
{ "centerSelection", BOOLEAN },
{ "scrollLoop", BOOLEAN },
{ "zIndex", FLOAT } } },
{ "gridtile", {
{ "size", NORMALIZED_PAIR },
{ "padding", NORMALIZED_PAIR },
@ -410,6 +418,25 @@ void ThemeData::parseElement(const pugi::xml_node& root, const std::map<std::str
switch(typeIt->second)
{
case NORMALIZED_RECT:
{
Vector4f val;
auto splits = Utils::String::delimitedStringToVector(str, " ");
if (splits.size() == 2)
{
val = Vector4f((float)atof(splits.at(0).c_str()), (float)atof(splits.at(1).c_str()),
(float)atof(splits.at(0).c_str()), (float)atof(splits.at(1).c_str()));
}
else if (splits.size() == 4)
{
val = Vector4f((float)atof(splits.at(0).c_str()), (float)atof(splits.at(1).c_str()),
(float)atof(splits.at(2).c_str()), (float)atof(splits.at(3).c_str()));
}
element.properties[node.name()] = val;
break;
}
case NORMALIZED_PAIR:
{
size_t divider = str.find(' ');

View file

@ -3,6 +3,7 @@
#define ES_CORE_THEME_DATA_H
#include "math/Vector2f.h"
#include "math/Vector4f.h"
#include "utils/FileSystemUtil.h"
#include <deque>
#include <map>
@ -94,12 +95,14 @@ public:
struct Property
{
void operator= (const Vector4f& value) { r = value; v = Vector2f(value.x(), value.y()); }
void operator= (const Vector2f& value) { v = value; }
void operator= (const std::string& value) { s = value; }
void operator= (const unsigned int& value) { i = value; }
void operator= (const float& value) { f = value; }
void operator= (const bool& value) { b = value; }
Vector4f r;
Vector2f v;
std::string s;
unsigned int i;
@ -117,6 +120,7 @@ public:
else if(std::is_same<T, unsigned int>::value) return *(const T*)&properties.at(prop).i;
else if(std::is_same<T, float>::value) return *(const T*)&properties.at(prop).f;
else if(std::is_same<T, bool>::value) return *(const T*)&properties.at(prop).b;
else if(std::is_same<T, Vector4f>::value) return *(const T*)&properties.at(prop).r;
return T();
}
@ -140,6 +144,7 @@ public:
enum ElementPropertyType
{
NORMALIZED_RECT,
NORMALIZED_PAIR,
PATH,
STRING,

View file

@ -1,5 +1,6 @@
#include "GridTileComponent.h"
#include "animations/LambdaAnimation.h"
#include "resources/TextureResource.h"
#include "ThemeData.h"
@ -29,7 +30,9 @@ GridTileComponent::GridTileComponent(Window* window) : GuiComponent(window), mBa
addChild(&mBackground);
addChild(&(*mImage));
setSelected(false);
mSelectedZoomPercent = 0;
setSelected(false, false);
setVisible(true);
}
@ -42,19 +45,53 @@ void GridTileComponent::render(const Transform4x4f& parentTrans)
}
// Update all the tile properties to the new status (selected or default)
void GridTileComponent::update()
void GridTileComponent::update(int deltaTime)
{
const GridTileProperties& currentProperties = getCurrentProperties();
GuiComponent::update(deltaTime);
mBackground.setImagePath(currentProperties.mBackgroundImage);
calcCurrentProperties();
mImage->setColorShift(currentProperties.mImageColor);
mBackground.setCenterColor(currentProperties.mBackgroundCenterColor);
mBackground.setEdgeColor(currentProperties.mBackgroundEdgeColor);
mBackground.setImagePath(mCurrentProperties.mBackgroundImage);
mImage->setColorShift(mCurrentProperties.mImageColor);
mBackground.setCenterColor(mCurrentProperties.mBackgroundCenterColor);
mBackground.setEdgeColor(mCurrentProperties.mBackgroundEdgeColor);
resize();
}
void applyThemeToProperties(const ThemeData::ThemeElement* elem, GridTileProperties properties)
{
Vector2f screen = Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
if (elem->has("size"))
properties.mSize = elem->get<Vector2f>("size") * screen;
if (elem->has("padding"))
properties.mPadding = elem->get<Vector2f>("padding");
if (elem->has("imageColor"))
properties.mImageColor = elem->get<unsigned int>("imageColor");
if (elem->has("backgroundImage"))
properties.mBackgroundImage = elem->get<std::string>("backgroundImage");
if (elem->has("backgroundCornerSize"))
properties.mBackgroundCornerSize = elem->get<Vector2f>("backgroundCornerSize");
if (elem->has("backgroundColor"))
{
properties.mBackgroundCenterColor = elem->get<unsigned int>("backgroundColor");
properties.mBackgroundEdgeColor = elem->get<unsigned int>("backgroundColor");
}
if (elem->has("backgroundCenterColor"))
properties.mBackgroundCenterColor = elem->get<unsigned int>("backgroundCenterColor");
if (elem->has("backgroundEdgeColor"))
properties.mBackgroundEdgeColor = elem->get<unsigned int>("backgroundEdgeColor");
}
void GridTileComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& /*element*/, unsigned int /*properties*/)
{
Vector2f screen = Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
@ -62,70 +99,20 @@ void GridTileComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, cons
// Apply theme to the default gridtile
const ThemeData::ThemeElement* elem = theme->getElement(view, "default", "gridtile");
if (elem)
{
if (elem->has("size"))
mDefaultProperties.mSize = elem->get<Vector2f>("size") * screen;
if (elem->has("padding"))
mDefaultProperties.mPadding = elem->get<Vector2f>("padding");
if (elem->has("imageColor"))
mDefaultProperties.mImageColor = elem->get<unsigned int>("imageColor");
if (elem->has("backgroundImage"))
mDefaultProperties.mBackgroundImage = elem->get<std::string>("backgroundImage");
if (elem->has("backgroundCornerSize"))
mDefaultProperties.mBackgroundCornerSize = elem->get<Vector2f>("backgroundCornerSize");
if (elem->has("backgroundColor"))
{
mDefaultProperties.mBackgroundCenterColor = elem->get<unsigned int>("backgroundColor");
mDefaultProperties.mBackgroundEdgeColor = elem->get<unsigned int>("backgroundColor");
}
if (elem->has("backgroundCenterColor"))
mDefaultProperties.mBackgroundCenterColor = elem->get<unsigned int>("backgroundCenterColor");
if (elem->has("backgroundEdgeColor"))
mDefaultProperties.mBackgroundEdgeColor = elem->get<unsigned int>("backgroundEdgeColor");
}
applyThemeToProperties(elem, mDefaultProperties);
// Apply theme to the selected gridtile
// NOTE that some of the default gridtile properties influence on the selected gridtile properties
// See THEMES.md for more informations
elem = theme->getElement(view, "selected", "gridtile");
mSelectedProperties.mSize = elem && elem->has("size") ?
elem->get<Vector2f>("size") * screen :
getSelectedTileSize();
mSelectedProperties.mSize = getSelectedTileSize();
mSelectedProperties.mPadding = mDefaultProperties.mPadding;
mSelectedProperties.mBackgroundImage = mDefaultProperties.mBackgroundImage;
mSelectedProperties.mBackgroundCornerSize = mDefaultProperties.mBackgroundCornerSize;
mSelectedProperties.mPadding = elem && elem->has("padding") ?
elem->get<Vector2f>("padding") :
mDefaultProperties.mPadding;
if (elem && elem->has("imageColor"))
mSelectedProperties.mImageColor = elem->get<unsigned int>("imageColor");
mSelectedProperties.mBackgroundImage = elem && elem->has("backgroundImage") ?
elem->get<std::string>("backgroundImage") :
mDefaultProperties.mBackgroundImage;
mSelectedProperties.mBackgroundCornerSize = elem && elem->has("backgroundCornerSize") ?
elem->get<Vector2f>("backgroundCornerSize") :
mDefaultProperties.mBackgroundCornerSize;
if (elem && elem->has("backgroundColor"))
{
mSelectedProperties.mBackgroundCenterColor = elem->get<unsigned int>("backgroundColor");
mSelectedProperties.mBackgroundEdgeColor = elem->get<unsigned int>("backgroundColor");
}
if (elem && elem->has("backgroundCenterColor"))
mSelectedProperties.mBackgroundCenterColor = elem->get<unsigned int>("backgroundCenterColor");
if (elem && elem->has("backgroundEdgeColor"))
mSelectedProperties.mBackgroundEdgeColor = elem->get<unsigned int>("backgroundEdgeColor");
if (elem)
applyThemeToProperties(elem, mSelectedProperties);
}
// Made this a static function because the ImageGridComponent need to know the default tile size
@ -147,6 +134,11 @@ bool GridTileComponent::isSelected() const
return mSelected;
}
void GridTileComponent::reset()
{
setImage("");
}
void GridTileComponent::setImage(const std::string& path)
{
mImage->setImage(path);
@ -163,9 +155,80 @@ void GridTileComponent::setImage(const std::shared_ptr<TextureResource>& texture
resize();
}
void GridTileComponent::setSelected(bool selected)
void GridTileComponent::setSelected(bool selected, bool allowAnimation, Vector3f* pPosition, bool force)
{
if (mSelected == selected && !force)
{
return;
}
mSelected = selected;
if (selected)
{
if (pPosition == NULL || !allowAnimation)
{
cancelAnimation(3);
this->setSelectedZoom(1);
mAnimPosition = Vector3f(0, 0, 0);
resize();
}
else
{
mAnimPosition = Vector3f(pPosition->x(), pPosition->y(), pPosition->z());
auto func = [this](float t)
{
t -= 1; // cubic ease out
float pct = Math::lerp(0, 1, t*t*t + 1);
this->setSelectedZoom(pct);
};
cancelAnimation(3);
setAnimation(new LambdaAnimation(func, 250), 0, [this] {
this->setSelectedZoom(1);
mAnimPosition = Vector3f(0, 0, 0);
}, false, 3);
}
}
else // if (!selected)
{
if (!allowAnimation)
{
cancelAnimation(3);
this->setSelectedZoom(0);
resize();
}
else
{
this->setSelectedZoom(1);
auto func = [this](float t)
{
t -= 1; // cubic ease out
float pct = Math::lerp(0, 1, t*t*t + 1);
this->setSelectedZoom(1.0 - pct);
};
cancelAnimation(3);
setAnimation(new LambdaAnimation(func, 250), 0, [this] {
this->setSelectedZoom(0);
}, false, 3);
}
}
}
void GridTileComponent::setSelectedZoom(float percent)
{
if (mSelectedZoomPercent == percent)
return;
mSelectedZoomPercent = percent;
resize();
}
void GridTileComponent::setVisible(bool visible)
@ -175,14 +238,86 @@ void GridTileComponent::setVisible(bool visible)
void GridTileComponent::resize()
{
const GridTileProperties& currentProperties = getCurrentProperties();
calcCurrentProperties();
mImage->setMaxSize(currentProperties.mSize - currentProperties.mPadding * 2);
mBackground.setCornerSize(currentProperties.mBackgroundCornerSize);
mBackground.fitTo(currentProperties.mSize - mBackground.getCornerSize() * 2);
mImage->setMaxSize(mCurrentProperties.mSize - mCurrentProperties.mPadding * 2);
mBackground.setCornerSize(mCurrentProperties.mBackgroundCornerSize);
mBackground.fitTo(mCurrentProperties.mSize - mBackground.getCornerSize() * 2);
}
const GridTileProperties& GridTileComponent::getCurrentProperties() const
unsigned int mixColors(unsigned int first, unsigned int second, float percent)
{
return mSelected ? mSelectedProperties : mDefaultProperties;
unsigned char alpha0 = (first >> 24) & 0xFF;
unsigned char blue0 = (first >> 16) & 0xFF;
unsigned char green0 = (first >> 8) & 0xFF;
unsigned char red0 = first & 0xFF;
unsigned char alpha1 = (second >> 24) & 0xFF;
unsigned char blue1 = (second >> 16) & 0xFF;
unsigned char green1 = (second >> 8) & 0xFF;
unsigned char red1 = second & 0xFF;
unsigned char alpha = (unsigned char)(alpha0 * (1.0 - percent) + alpha1 * percent);
unsigned char blue = (unsigned char)(blue0 * (1.0 - percent) + blue1 * percent);
unsigned char green = (unsigned char)(green0 * (1.0 - percent) + green1 * percent);
unsigned char red = (unsigned char)(red0 * (1.0 - percent) + red1 * percent);
return (alpha << 24) | (blue << 16) | (green << 8) | red;
}
void GridTileComponent::calcCurrentProperties()
{
mCurrentProperties = mSelected ? mSelectedProperties : mDefaultProperties;
float zoomPercentInverse = 1.0 - mSelectedZoomPercent;
if (mSelectedZoomPercent != 0.0f && mSelectedZoomPercent != 1.0f) {
if (mDefaultProperties.mSize != mSelectedProperties.mSize) {
mCurrentProperties.mSize = mDefaultProperties.mSize * zoomPercentInverse + mSelectedProperties.mSize * mSelectedZoomPercent;
}
if (mDefaultProperties.mPadding != mSelectedProperties.mPadding)
{
mCurrentProperties.mPadding = mDefaultProperties.mPadding * zoomPercentInverse + mSelectedProperties.mPadding * mSelectedZoomPercent;
}
if (mDefaultProperties.mImageColor != mSelectedProperties.mImageColor)
{
mCurrentProperties.mImageColor = mixColors(mDefaultProperties.mImageColor, mSelectedProperties.mImageColor, mSelectedZoomPercent);
}
if (mDefaultProperties.mBackgroundCornerSize != mSelectedProperties.mBackgroundCornerSize)
{
mCurrentProperties.mBackgroundCornerSize = mDefaultProperties.mBackgroundCornerSize * zoomPercentInverse + mSelectedProperties.mBackgroundCornerSize * mSelectedZoomPercent;
}
if (mDefaultProperties.mBackgroundCenterColor != mSelectedProperties.mBackgroundCenterColor)
{
mCurrentProperties.mBackgroundCenterColor = mixColors(mDefaultProperties.mBackgroundCenterColor, mSelectedProperties.mBackgroundCenterColor, mSelectedZoomPercent);
}
if (mDefaultProperties.mBackgroundEdgeColor != mSelectedProperties.mBackgroundEdgeColor)
{
mCurrentProperties.mBackgroundEdgeColor = mixColors(mDefaultProperties.mBackgroundEdgeColor, mSelectedProperties.mBackgroundEdgeColor, mSelectedZoomPercent);
}
}
}
Vector3f GridTileComponent::getBackgroundPosition()
{
return mBackground.getPosition() + mPosition;
}
std::shared_ptr<TextureResource> GridTileComponent::getTexture()
{
if (mImage != nullptr)
return mImage->getTexture();
return nullptr;
};
void GridTileComponent::forceSize(Vector2f size, float selectedZoom)
{
mDefaultProperties.mSize = size;
mSelectedProperties.mSize = size * selectedZoom;
}

View file

@ -22,7 +22,6 @@ public:
GridTileComponent(Window* window);
void render(const Transform4x4f& parentTrans) override;
void update();
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties);
// Made this a static function because the ImageGridComponent need to know the default tile max size
@ -31,23 +30,38 @@ public:
Vector2f getSelectedTileSize() const;
bool isSelected() const;
void reset();
void setImage(const std::string& path);
void setImage(const std::shared_ptr<TextureResource>& texture);
void setSelected(bool selected);
void setSelected(bool selected, bool allowAnimation = true, Vector3f* pPosition = NULL, bool force=false);
void setVisible(bool visible);
void forceSize(Vector2f size, float selectedZoom);
Vector3f getBackgroundPosition();
virtual void update(int deltaTime);
std::shared_ptr<TextureResource> getTexture();
private:
void resize();
const GridTileProperties& getCurrentProperties() const;
void calcCurrentProperties();
void setSelectedZoom(float percent);
std::shared_ptr<ImageComponent> mImage;
NinePatchComponent mBackground;
GridTileProperties mDefaultProperties;
GridTileProperties mSelectedProperties;
GridTileProperties mCurrentProperties;
float mSelectedZoomPercent;
bool mSelected;
bool mVisible;
Vector3f mAnimPosition;
};
#endif // ES_CORE_COMPONENTS_GRID_TILE_COMPONENT_H

View file

@ -75,6 +75,8 @@ public:
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override;
virtual std::vector<HelpPrompt> getHelpPrompts() override;
std::shared_ptr<TextureResource> getTexture() { return mTexture; };
private:
Vector2f mTargetSize;

View file

@ -3,16 +3,26 @@
#define ES_CORE_COMPONENTS_IMAGE_GRID_COMPONENT_H
#include "Log.h"
#include "animations/LambdaAnimation.h"
#include "components/IList.h"
#include "resources/TextureResource.h"
#include "GridTileComponent.h"
#define EXTRAITEMS 2
enum ScrollDirection
{
SCROLL_VERTICALLY,
SCROLL_HORIZONTALLY
};
enum ImageSource
{
THUMBNAIL,
IMAGE,
MARQUEE
};
struct ImageGridData
{
std::string texturePath;
@ -50,20 +60,23 @@ public:
void onSizeChanged() override;
inline void setCursorChangedCallback(const std::function<void(CursorState state)>& func) { mCursorChangedCallback = func; }
ImageSource getImageSource() { return mImageSource; };
protected:
virtual void onCursorChanged(const CursorState& state) override;
private:
// TILES
void buildTiles();
void updateTiles();
void updateTileAtPos(int tilePos, int imgPos, int bufferTop, int bufferBot);
int getStartPosition() const;
void updateTiles(bool ascending = true, bool allowAnimation = true, bool updateSelectedState = true);
void updateTileAtPos(int tilePos, int imgPos, bool allowAnimation, bool updateSelectedState);
void calcGridDimension();
bool isScrollLoop();
bool isVertical() { return mScrollDirection == SCROLL_VERTICALLY; };
// IMAGES & ENTRIES
const int texBuffersBehind[4] = { 1, 1, 1, 1 };
const int texBuffersForward[4] = { 1, 2, 3, 3 };
bool mEntriesDirty;
int mLastCursor;
std::string mDefaultGameTexture;
@ -71,14 +84,27 @@ private:
// TILES
bool mLastRowPartial;
Vector2f mAutoLayout;
float mAutoLayoutZoom;
Vector4f mPadding;
Vector2f mMargin;
Vector2f mTileSize;
Vector2i mGridDimension;
std::shared_ptr<ThemeData> mTheme;
std::vector< std::shared_ptr<GridTileComponent> > mTiles;
int mStartPosition;
float mCamera;
float mCameraDirection;
// MISCELLANEOUS
bool mAnimate;
bool mCenterSelection;
bool mScrollLoop;
ScrollDirection mScrollDirection;
ImageSource mImageSource;
std::function<void(CursorState state)> mCursorChangedCallback;
};
@ -87,6 +113,14 @@ ImageGridComponent<T>::ImageGridComponent(Window* window) : IList<ImageGridData,
{
Vector2f screen = Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
mCamera = 0.0;
mCameraDirection = 1.0;
mAutoLayout = Vector2f::Zero();
mAutoLayoutZoom = 1.0;
mStartPosition = 0;
mEntriesDirty = true;
mLastCursor = 0;
mDefaultGameTexture = ":/cartridge.svg";
@ -94,9 +128,14 @@ ImageGridComponent<T>::ImageGridComponent(Window* window) : IList<ImageGridData,
mSize = screen * 0.80f;
mMargin = screen * 0.07f;
mPadding = Vector4f::Zero();
mTileSize = GridTileComponent::getDefaultTileSize();
mAnimate = true;
mCenterSelection = false;
mScrollLoop = false;
mScrollDirection = SCROLL_VERTICALLY;
mImageSource = THUMBNAIL;
}
template<typename T>
@ -116,19 +155,24 @@ bool ImageGridComponent<T>::input(InputConfig* config, Input input)
{
if(input.value != 0)
{
int idx = isVertical() ? 0 : 1;
Vector2i dir = Vector2i::Zero();
if(config->isMappedLike("up", input))
dir[1 ^ mScrollDirection] = -1;
dir[1 ^ idx] = -1;
else if(config->isMappedLike("down", input))
dir[1 ^ mScrollDirection] = 1;
dir[1 ^ idx] = 1;
else if(config->isMappedLike("left", input))
dir[0 ^ mScrollDirection] = -1;
dir[0 ^ idx] = -1;
else if(config->isMappedLike("right", input))
dir[0 ^ mScrollDirection] = 1;
dir[0 ^ idx] = 1;
if(dir != Vector2i::Zero())
{
listInput(dir.x() + dir.y() * mGridDimension.x());
if (isVertical())
listInput(dir.x() + dir.y() * mGridDimension.x());
else
listInput(dir.x() + dir.y() * mGridDimension.y());
return true;
}
}else{
@ -144,16 +188,23 @@ bool ImageGridComponent<T>::input(InputConfig* config, Input input)
template<typename T>
void ImageGridComponent<T>::update(int deltaTime)
{
GuiComponent::update(deltaTime);
listUpdate(deltaTime);
for(auto it = mTiles.begin(); it != mTiles.end(); it++)
(*it)->update();
(*it)->update(deltaTime);
}
template<typename T>
void ImageGridComponent<T>::render(const Transform4x4f& parentTrans)
{
Transform4x4f trans = getTransform() * parentTrans;
Transform4x4f tileTrans = trans;
float offsetX = isVertical() ? 0.0f : mCamera * mCameraDirection * (mTileSize.x() + mMargin.x());
float offsetY = isVertical() ? mCamera * mCameraDirection * (mTileSize.y() + mMargin.y()) : 0.0f;
tileTrans.translate(Vector3f(offsetX, offsetY, 0.0));
if(mEntriesDirty)
{
@ -180,14 +231,14 @@ void ImageGridComponent<T>::render(const Transform4x4f& parentTrans)
if(tile->isSelected())
selectedTile = tile;
else
tile->render(trans);
tile->render(tileTrans);
}
Renderer::popClipRect();
// Render the selected image on top of the others
if (selectedTile != NULL)
selectedTile->render(trans);
selectedTile->render(tileTrans);
listRenderTitleOverlay(trans);
@ -211,9 +262,44 @@ void ImageGridComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
if (elem->has("margin"))
mMargin = elem->get<Vector2f>("margin") * screen;
if (elem->has("padding"))
mPadding = elem->get<Vector4f>("padding") * Vector4f(screen.x(), screen.y(), screen.x(), screen.y());
if (elem->has("autoLayout"))
mAutoLayout = elem->get<Vector2f>("autoLayout");
if (elem->has("autoLayoutSelectedZoom"))
mAutoLayoutZoom = elem->get<float>("autoLayoutSelectedZoom");
if (elem->has("imageSource"))
{
auto direction = elem->get<std::string>("imageSource");
if (direction == "image")
mImageSource = IMAGE;
else if (direction == "marquee")
mImageSource = MARQUEE;
else
mImageSource = THUMBNAIL;
}
else
mImageSource = THUMBNAIL;
if (elem->has("scrollDirection"))
mScrollDirection = (ScrollDirection)(elem->get<std::string>("scrollDirection") == "horizontal");
if (elem->has("centerSelection"))
{
mCenterSelection = (elem->get<bool>("centerSelection"));
if (elem->has("scrollLoop"))
mScrollLoop = (elem->get<bool>("scrollLoop"));
}
if (elem->has("animate"))
mAnimate = (elem->get<bool>("animate"));
else
mAnimate = true;
if (elem->has("gameImage"))
{
std::string path = elem->get<std::string>("gameImage");
@ -283,39 +369,191 @@ void ImageGridComponent<T>::onSizeChanged()
template<typename T>
void ImageGridComponent<T>::onCursorChanged(const CursorState& state)
{
updateTiles();
if (mLastCursor == mCursor)
{
if (state == CURSOR_STOPPED && mCursorChangedCallback)
mCursorChangedCallback(state);
if(mCursorChangedCallback)
return;
}
bool direction = mCursor >= mLastCursor;
int diff = direction ? mCursor - mLastCursor : mLastCursor - mCursor;
if (isScrollLoop() && diff == mEntries.size() - 1)
{
direction = !direction;
}
int oldStart = mStartPosition;
int dimScrollable = (isVertical() ? mGridDimension.y() : mGridDimension.x()) - 2 * EXTRAITEMS;
int dimOpposite = isVertical() ? mGridDimension.x() : mGridDimension.y();
int centralCol = (int)(dimScrollable - 0.5) / 2;
int maxCentralCol = dimScrollable / 2;
int oldCol = (mLastCursor / dimOpposite);
int col = (mCursor / dimOpposite);
int lastCol = ((mEntries.size() - 1) / dimOpposite);
int lastScroll = std::max(0, (lastCol + 1 - dimScrollable));
float startPos = 0;
float endPos = 1;
if (((GuiComponent*)this)->isAnimationPlaying(2))
{
startPos = 0;
((GuiComponent*)this)->cancelAnimation(2);
updateTiles(direction, false, false);
}
if (mAnimate) {
std::shared_ptr<GridTileComponent> oldTile = nullptr;
std::shared_ptr<GridTileComponent> newTile = nullptr;
int oldIdx = mLastCursor - mStartPosition + (dimOpposite * EXTRAITEMS);
if (oldIdx >= 0 && oldIdx < mTiles.size())
oldTile = mTiles[oldIdx];
int newIdx = mCursor - mStartPosition + (dimOpposite * EXTRAITEMS);
if (isScrollLoop()) {
if (newIdx < 0)
newIdx += mEntries.size();
else if (newIdx >= mTiles.size())
newIdx -= mEntries.size();
}
if (newIdx >= 0 && newIdx < mTiles.size())
newTile = mTiles[newIdx];
for (auto it = mTiles.begin(); it != mTiles.end(); it++) {
if ((*it)->isSelected() && *it != oldTile && *it != newTile) {
startPos = 0;
(*it)->setSelected(false, false, nullptr);
}
}
Vector3f oldPos = Vector3f::Zero();
if (oldTile != nullptr && oldTile != newTile) {
oldPos = oldTile->getBackgroundPosition();
oldTile->setSelected(false, true, nullptr, true);
}
if (newTile != nullptr)
newTile->setSelected(true, true, oldPos == Vector3f::Zero() ? nullptr : &oldPos, true);
}
int firstVisibleCol = mStartPosition / dimOpposite;
if ((col < centralCol || (col == 0 && col == centralCol)) && !mCenterSelection)
mStartPosition = 0;
else if ((col - centralCol) > lastScroll && !mCenterSelection && !isScrollLoop())
mStartPosition = lastScroll * dimOpposite;
else if ((maxCentralCol != centralCol && col == firstVisibleCol + maxCentralCol) || col == firstVisibleCol + centralCol)
{
if (col == firstVisibleCol + maxCentralCol)
mStartPosition = (col - maxCentralCol) * dimOpposite;
else
mStartPosition = (col - centralCol) * dimOpposite;
}
else
{
if (oldCol == firstVisibleCol + maxCentralCol)
mStartPosition = (col - maxCentralCol) * dimOpposite;
else
mStartPosition = (col - centralCol) * dimOpposite;
}
auto lastCursor = mLastCursor;
mLastCursor = mCursor;
mCameraDirection = direction ? -1.0f : 1.0f;
mCamera = 0;
if (lastCursor < 0 || !mAnimate)
{
updateTiles(direction, mAnimate && (lastCursor >= 0 || isScrollLoop()));
if (mCursorChangedCallback)
mCursorChangedCallback(state);
return;
}
if (mCursorChangedCallback)
mCursorChangedCallback(state);
bool moveCamera = (oldStart != mStartPosition);
auto func = [this, startPos, endPos, moveCamera](float t)
{
if (!moveCamera)
return;
t -= 1; // cubic ease out
float pct = Math::lerp(0, 1, t*t*t + 1);
t = startPos * (1.0f - pct) + endPos * pct;
mCamera = t;
};
((GuiComponent*)this)->setAnimation(new LambdaAnimation(func, 250), 0, [this, direction] {
mCamera = 0;
updateTiles(direction, false);
}, false, 2);
}
// Create and position tiles (mTiles)
template<typename T>
void ImageGridComponent<T>::buildTiles()
{
mStartPosition = 0;
mTiles.clear();
calcGridDimension();
if (mCenterSelection)
{
int dimScrollable = (isVertical() ? mGridDimension.y() : mGridDimension.x()) - 2 * EXTRAITEMS;
mStartPosition -= (int) Math::floorf(dimScrollable / 2.0f);
}
Vector2f tileDistance = mTileSize + mMargin;
Vector2f bufferSize = Vector2f(mScrollDirection == SCROLL_HORIZONTALLY ? tileDistance.x() * texBuffersForward[3] : 0,
mScrollDirection == SCROLL_VERTICALLY ? tileDistance.y() * texBuffersForward[3] : 0);
Vector2f startPosition = mTileSize / 2 - bufferSize;
if (mAutoLayout.x() != 0 && mAutoLayout.y() != 0)
{
auto x = (mSize.x() - (mMargin.x() * (mAutoLayout.x() - 1)) - mPadding.x() - mPadding.z()) / (int) mAutoLayout.x();
auto y = (mSize.y() - (mMargin.y() * (mAutoLayout.y() - 1)) - mPadding.y() - mPadding.w()) / (int) mAutoLayout.y();
mTileSize = Vector2f(x, y);
tileDistance = mTileSize + mMargin;
}
bool vert = isVertical();
Vector2f startPosition = mTileSize / 2;
startPosition += mPadding.v2();
int X, Y;
// Layout tile size and position
for(int y = 0; y < mGridDimension.y(); y++)
for (int y = 0; y < (vert ? mGridDimension.y() : mGridDimension.x()); y++)
{
for(int x = 0; x < mGridDimension.x(); x++)
for (int x = 0; x < (vert ? mGridDimension.x() : mGridDimension.y()); x++)
{
// Create tiles
auto tile = std::make_shared<GridTileComponent>(mWindow);
// In Vertical mod, tiles are ordered from left to right, then from top to bottom
// In Horizontal mod, tiles are ordered from top to bottom, then from left to right
X = mScrollDirection == SCROLL_VERTICALLY ? x : y;
Y = mScrollDirection == SCROLL_VERTICALLY ? y : x;
X = vert ? x : y - EXTRAITEMS;
Y = vert ? y - EXTRAITEMS : x;
tile->setPosition(X * tileDistance.x() + startPosition.x(), Y * tileDistance.y() + startPosition.y());
tile->setOrigin(0.5f, 0.5f);
@ -324,13 +562,16 @@ void ImageGridComponent<T>::buildTiles()
if (mTheme)
tile->applyTheme(mTheme, "grid", "gridtile", ThemeFlags::ALL);
if (mAutoLayout.x() != 0 && mAutoLayout.y() != 0)
tile->forceSize(mTileSize, mAutoLayoutZoom);
mTiles.push_back(tile);
}
}
}
template<typename T>
void ImageGridComponent<T>::updateTiles()
void ImageGridComponent<T>::updateTiles(bool ascending, bool allowAnimation, bool updateSelectedState)
{
if (!mTiles.size())
return;
@ -349,106 +590,93 @@ void ImageGridComponent<T>::updateTiles()
return;
}
// 1 if scrolling down, -1 if scrolling up
int scrollDirection = mCursor >= mLastCursor ? 1 : -1;
// Temporary store previous texture so they can't be unloaded
std::vector<std::shared_ptr<TextureResource>> previousTextures;
for (int ti = 0; ti < (int)mTiles.size(); ti++)
{
std::shared_ptr<GridTileComponent> tile = mTiles.at(ti);
previousTextures.push_back(tile->getTexture());
}
// If going down, update from top to bottom
// If going up, update from bottom to top
int ti = scrollDirection == 1 ? 0 : (int)mTiles.size() - 1;
int end = scrollDirection == 1 ? (int)mTiles.size() : -1;
int scrollDirection = ascending ? 1 : -1;
int ti = ascending ? 0 : (int)mTiles.size() - 1;
int end = ascending ? (int)mTiles.size() : -1;
int img = mStartPosition + ti;
int img = getStartPosition();
if (scrollDirection == -1)
img += (int)mTiles.size() - 1;
// Calculate buffer size depending on scroll speed and direction
int bufferBehind = (texBuffersForward[3] - texBuffersBehind[mScrollTier]) * mGridDimension.x();
int bufferForward = (texBuffersForward[3] - texBuffersForward[mScrollTier]) * mGridDimension.x();
int bufferTop = scrollDirection == 1 ? bufferBehind : bufferForward;
int bufferBot = scrollDirection == 1 ? bufferForward : bufferBehind;
img -= EXTRAITEMS * (isVertical() ? mGridDimension.x() : mGridDimension.y());
// Update the tiles
while (ti != end)
{
updateTileAtPos(ti, img, bufferTop, bufferBot);
updateTileAtPos(ti, img, allowAnimation, updateSelectedState);
ti += scrollDirection;
img += scrollDirection;
}
if (updateSelectedState)
mLastCursor = mCursor;
mLastCursor = mCursor;
}
template<typename T>
void ImageGridComponent<T>::updateTileAtPos(int tilePos, int imgPos, int bufferTop, int bufferBot)
void ImageGridComponent<T>::updateTileAtPos(int tilePos, int imgPos, bool allowAnimation, bool updateSelectedState)
{
std::shared_ptr<GridTileComponent> tile = mTiles.at(tilePos);
// If we have more tiles than we have to display images on screen, hide them
if(imgPos < 0 || imgPos >= size()
|| tilePos < bufferTop || tilePos >= (int)mTiles.size() - bufferBot) // Same for tiles out of the buffer
if(isScrollLoop())
{
tile->setSelected(false);
tile->setImage("");
if (imgPos < 0)
imgPos += mEntries.size();
else if (imgPos >= size())
imgPos -= mEntries.size();
}
// If we have more tiles than we have to display images on screen, hide them
if(imgPos < 0 || imgPos >= size() || tilePos < 0 || tilePos >= (int) mTiles.size()) // Same for tiles out of the buffer
{
if (updateSelectedState)
tile->setSelected(false, allowAnimation);
tile->reset();
tile->setVisible(false);
}
else
{
tile->setSelected(imgPos == mCursor);
tile->setVisible(true);
std::string imagePath = mEntries.at(imgPos).data.texturePath;
if (ResourceManager::getInstance()->fileExists(imagePath))
{
tile->setImage(imagePath);
}
else if (mEntries.at(imgPos).object->getType() == 2)
tile->setImage(mDefaultFolderTexture);
else
tile->setImage(mDefaultGameTexture);
if (updateSelectedState)
{
// FileType::FOLDER = 2, but FileData is our template parameter T,
// so we don't want to bring that dependence to FileData here
if (mEntries.at(imgPos).object->getType() == 2)
tile->setImage(mDefaultFolderTexture);
if (imgPos == mCursor && mCursor != mLastCursor)
{
int dif = mCursor - tilePos;
int idx = mLastCursor - dif;
if (idx < 0 || idx >= mTiles.size())
idx = 0;
Vector3f pos = mTiles.at(idx)->getBackgroundPosition();
tile->setSelected(true, allowAnimation, &pos);
}
else
tile->setImage(mDefaultGameTexture);
tile->setSelected(imgPos == mCursor, allowAnimation);
}
}
}
// Return the starting position (the number of the game which will be displayed on top left of the screen)
template<typename T>
int ImageGridComponent<T>::getStartPosition() const
{
// The "partialRow" variable exist because we want to keep the same positioning behavior in both
// case, whenever we have an integer number of rows or not (the last partial row is ignored when
// calculating position and the cursor shouldn't end up in this row when close to the end)
int partialRow = (int)mLastRowPartial;
int cursorRow = mCursor / mGridDimension.x();
int start = (cursorRow - ((mGridDimension.y() - partialRow) / 2)) * mGridDimension.x();
// Number of tiles which are just used as a buffer for texture loading
int bufferSize = texBuffersForward[3] * mGridDimension.x();
if(start + (mGridDimension.x() * (mGridDimension.y() - partialRow)) >= (int)mEntries.size() + bufferSize)
{
// If we are at the end put the row as close as we can and no higher, using the following formula
// Where E is the nb of entries, X the grid x dim (nb of column), Y the grid y dim (nb of line)
// start = first tile of last row - nb column * (nb line - 1)
// = (E - 1) / X * X - X * (Y - 1)
// = X * ((E - 1) / X - Y + 1)
start = mGridDimension.x() * (((int)mEntries.size() - 1) / mGridDimension.x() - mGridDimension.y() + 1 + partialRow) + bufferSize;
}
if(start < -bufferSize)
{
start = -bufferSize;
}
return start;
}
// Calculate how much tiles of size mTileSize we can fit in a grid of size mSize using a margin of size mMargin
template<typename T>
void ImageGridComponent<T>::calcGridDimension()
@ -457,24 +685,34 @@ void ImageGridComponent<T>::calcGridDimension()
// <=> COLUMNS = (GRID_SIZE + MARGIN) / (TILE_SIZE + MARGIN)
Vector2f gridDimension = (mSize + mMargin) / (mTileSize + mMargin);
if (mAutoLayout.x() != 0 && mAutoLayout.y() != 0)
gridDimension = mAutoLayout;
mLastRowPartial = Math::floorf(gridDimension.y()) != gridDimension.y();
// Ceil y dim so we can display partial last row
mGridDimension = Vector2i(gridDimension.x(), Math::ceilf(gridDimension.y()));
// Invert dimensions for horizontally scrolling grid
if (mScrollDirection == SCROLL_HORIZONTALLY)
mGridDimension = Vector2i(mGridDimension.y(), mGridDimension.x());
// Grid dimension validation
if (mGridDimension.x() < 1)
LOG(LogError) << "Theme defined grid X dimension below 1";
if (mGridDimension.y() < 1)
LOG(LogError) << "Theme defined grid Y dimension below 1";
// Add extra tiles to both side depending on max texture buffer
mGridDimension.y() += texBuffersForward[3] * 2;
// Add extra tiles to both sides : Add EXTRAITEMS before, EXTRAITEMS after
if (isVertical())
mGridDimension.y() += 2 * EXTRAITEMS;
else
mGridDimension.x() += 2 * EXTRAITEMS;
}
template<typename T>
bool ImageGridComponent<T>::isScrollLoop() {
if (!mScrollLoop)
return false;
if (isVertical())
return (mGridDimension.x() * (mGridDimension.y() - 2 * EXTRAITEMS)) <= mEntries.size();
return (mGridDimension.y() * (mGridDimension.x() - 2 * EXTRAITEMS)) <= mEntries.size();
};
#endif // ES_CORE_COMPONENTS_IMAGE_GRID_COMPONENT_H

View file

@ -222,24 +222,30 @@ namespace Utils
} // removeParenthesis
stringVector commaStringToVector(const std::string& _string)
stringVector delimitedStringToVector(const std::string& _string, const std::string& _delimiter, bool sort)
{
stringVector vector;
size_t start = 0;
size_t comma = _string.find(",");
size_t comma = _string.find(_delimiter);
while(comma != std::string::npos)
{
vector.push_back(_string.substr(start, comma - start));
start = comma + 1;
comma = _string.find(",", start);
comma = _string.find(_delimiter, start);
}
vector.push_back(_string.substr(start));
std::sort(vector.begin(), vector.end());
if (sort)
std::sort(vector.begin(), vector.end());
return vector;
} // delimitedStringToVector
stringVector commaStringToVector(const std::string& _string, bool sort)
{
return delimitedStringToVector(_string, ",", sort);
} // commaStringToVector
std::string vectorToCommaString(stringVector _vector)

View file

@ -11,22 +11,23 @@ namespace Utils
{
typedef std::vector<std::string> stringVector;
unsigned int chars2Unicode (const std::string& _string, size_t& _cursor);
std::string unicode2Chars (const unsigned int _unicode);
size_t nextCursor (const std::string& _string, const size_t _cursor);
size_t prevCursor (const std::string& _string, const size_t _cursor);
size_t moveCursor (const std::string& _string, const size_t _cursor, const int _amount);
std::string toLower (const std::string& _string);
std::string toUpper (const std::string& _string);
std::string trim (const std::string& _string);
std::string replace (const std::string& _string, const std::string& _replace, const std::string& _with);
bool startsWith (const std::string& _string, const std::string& _start);
bool endsWith (const std::string& _string, const std::string& _end);
std::string removeParenthesis (const std::string& _string);
stringVector commaStringToVector(const std::string& _string);
std::string vectorToCommaString(stringVector _vector);
std::string format (const char* _string, ...);
std::string scramble (const std::string& _input, const std::string& key);
unsigned int chars2Unicode (const std::string& _string, size_t& _cursor);
std::string unicode2Chars (const unsigned int _unicode);
size_t nextCursor (const std::string& _string, const size_t _cursor);
size_t prevCursor (const std::string& _string, const size_t _cursor);
size_t moveCursor (const std::string& _string, const size_t _cursor, const int _amount);
std::string toLower (const std::string& _string);
std::string toUpper (const std::string& _string);
std::string trim (const std::string& _string);
std::string replace (const std::string& _string, const std::string& _replace, const std::string& _with);
bool startsWith (const std::string& _string, const std::string& _start);
bool endsWith (const std::string& _string, const std::string& _end);
std::string removeParenthesis (const std::string& _string);
stringVector delimitedStringToVector(const std::string& _string, const std::string& _delimiter, bool sort = false);
stringVector commaStringToVector (const std::string& _string, bool sort = false);
std::string vectorToCommaString (stringVector _vector);
std::string format (const char* _string, ...);
std::string scramble (const std::string& _input, const std::string& key);
} // String::