Add GridTileComponent and base theming syntax

- Add the GridTileComponent which hold the image and its background
- Add base theming syntax for the ImageGrid and GridTIle
- Numerous refactoring/cleaning in ImageGridComponent
This commit is contained in:
Koerty 2018-04-07 21:23:10 +02:00
parent 61da776b71
commit 3993ace607
7 changed files with 332 additions and 79 deletions

View file

@ -281,6 +281,7 @@ You can now change the order in which elements are rendered by setting `zIndex`
* `image name="background"` - 0
* Extra Elements `extra="true"` - 10
* `textlist name="gamelist"` - 20
* `imagegrid name="gamegrid"` - 20
* Media
* `image name="md_image"` - 30
* `video name="md_video"` - 30
@ -472,7 +473,11 @@ Reference
* `image name="logo"` - ALL
- A header image. If a non-empty `path` is specified, `text name="headerText"` will be hidden and this image will be, by default, displayed roughly in its place.
* `imagegrid name="gamegrid"` - ALL
- The gamegrid.
- The gamegrid. The number of tile displayed is controlled by its size, margin and the default tile max size.
* `gridtile name="default"` - ALL
- Note that many of the default gridtile parameters change the selected gridtile parameters if they are not explicitly set by the theme. For example, changing the background image of the default gridtile also change the background image of the selected gridtile. Refer to the gridtile documentation for more informations.
* `gridtile name="selected"` - ALL
- See default gridtile description right above.
* Metadata
* Labels
@ -572,6 +577,20 @@ 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.
#### gridtile
* `size` - type: NORMALIZED_PAIR.
- The size of the default gridtile is used to calculate how many tiles can fit in the imagegrid. If not explicitly set, the size of the selected gridtile is equal the size of the default gridtile * 1.2
* `padding` - type: NORMALIZED_PAIR.
- The padding around the gridtile content. Default `16 16`. If not explicitly set, the selected tile padding will be equal to the default tile padding.
* `backgroundImage` - type: PATH.
- If not explicitly set, the selected tile background image will be the same as the default tile background image.
* `imageColor` - type: COLOR.
- The default tile image color and selected tile image color have no influence on each others.
* `backgroundColor` - type: COLOR.
- The default tile background color and selected tile background color have no influence on each others.
#### video

View file

@ -21,6 +21,7 @@ GridGameListView::GridGameListView(Window* window, FileData* root) :
const float padding = 0.01f;
mGrid.setPosition(mSize.x() * 0.1f, mSize.y() * 0.1f);
mGrid.setDefaultZIndex(20);
mGrid.setCursorChangedCallback([&](const CursorState& /*state*/) { updateInfoPanel(); });
addChild(&mGrid);

View file

@ -33,6 +33,7 @@ set(CORE_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/HelpComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/IList.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageGridComponent.h
@ -106,6 +107,7 @@ set(CORE_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/HelpComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/MenuComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/NinePatchComponent.cpp

View file

@ -27,7 +27,14 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>> The
{ "zIndex", FLOAT } } },
{ "imagegrid", {
{ "pos", NORMALIZED_PAIR },
{ "size", NORMALIZED_PAIR } } },
{ "size", NORMALIZED_PAIR },
{ "margin", NORMALIZED_PAIR } } },
{ "gridtile", {
{ "size", NORMALIZED_PAIR },
{ "padding", NORMALIZED_PAIR },
{ "backgroundImage", PATH },
{ "imageColor", COLOR },
{ "backgroundColor", COLOR } } },
{ "text", {
{ "pos", NORMALIZED_PAIR },
{ "size", NORMALIZED_PAIR },
@ -128,7 +135,7 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>> The
};
#define MINIMUM_THEME_FORMAT_VERSION 3
#define CURRENT_THEME_FORMAT_VERSION 5
#define CURRENT_THEME_FORMAT_VERSION 6
// helper
unsigned int getHexColor(const char* str)

View file

@ -0,0 +1,153 @@
#include "GridTileComponent.h"
#include "resources/TextureResource.h"
#include "ThemeData.h"
#include "Renderer.h"
GridTileComponent::GridTileComponent(Window* window) : GuiComponent(window), mBackground(window)
{
mDefaultProperties.mSize = getDefaultTileSize();
mDefaultProperties.mPadding = Vector2f(16.0f, 16.0f);
mDefaultProperties.mBackgroundImage = ":/frame.png";
mDefaultProperties.mImageColor = 0xAAAAAABB;
mDefaultProperties.mBackgroundColor = 0xAAAAEEFF;
mSelectedProperties.mSize = getSelectedTileSize();
mSelectedProperties.mPadding = mDefaultProperties.mPadding;
mSelectedProperties.mBackgroundImage = mDefaultProperties.mBackgroundImage;
mSelectedProperties.mImageColor = 0xFFFFFFFF;
mSelectedProperties.mBackgroundColor = 0xFFFFFFFF;
mImage = std::make_shared<ImageComponent>(mWindow);
mImage->setOrigin(0.5f, 0.5f);
mBackground.setOrigin(0.5f, 0.5f);
addChild(&mBackground);
addChild(&(*mImage));
setSelected(false);
setVisible(true);
}
void GridTileComponent::render(const Transform4x4f& parentTrans)
{
Transform4x4f trans = getTransform() * parentTrans;
if (mVisible)
renderChildren(trans);
}
// Update all the tile properties to the new status (selected or default)
void GridTileComponent::update()
{
const GridTileProperties& currentProperties = getCurrentProperties();
mBackground.setImagePath(currentProperties.mBackgroundImage);
mImage->setColorShift(currentProperties.mImageColor);
mBackground.setCenterColor(currentProperties.mBackgroundColor);
mBackground.setEdgeColor(currentProperties.mBackgroundColor);
resize();
}
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());
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("backgroundImage"))
mDefaultProperties.mBackgroundImage = elem->get<std::string>("backgroundImage");
if (elem->has("imageColor"))
mDefaultProperties.mImageColor = elem->get<unsigned int>("imageColor");
if (elem->has("backgroundColor"))
mDefaultProperties.mBackgroundColor = elem->get<unsigned int>("backgroundColor");
}
elem = theme->getElement(view, "selected", "gridtile");
mSelectedProperties.mSize = elem && elem->has("size") ?
elem->get<Vector2f>("size") * screen :
getSelectedTileSize();
mSelectedProperties.mPadding = elem && elem->has("padding") ?
elem->get<Vector2f>("padding") :
mDefaultProperties.mPadding;
mSelectedProperties.mBackgroundImage = elem && elem->has("backgroundImage") ?
elem->get<std::string>("backgroundImage") :
mDefaultProperties.mBackgroundImage;
if (elem && elem->has("imageColor"))
mSelectedProperties.mImageColor = elem->get<unsigned int>("imageColor");
if (elem && elem->has("backgroundColor"))
mSelectedProperties.mBackgroundColor = elem->get<unsigned int>("backgroundColor");
}
// Made this a static function because the ImageGridComponent need to know the default tile size
// to calculate the grid dimension before it instantiate the GridTileComponents
Vector2f GridTileComponent::getDefaultTileSize()
{
Vector2f screen = Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
return screen * 0.22f;
}
Vector2f GridTileComponent::getSelectedTileSize() const
{
return mDefaultProperties.mSize * 1.2f;
}
bool GridTileComponent::isSelected() const
{
return mSelected;
}
void GridTileComponent::setImage(const std::string& path)
{
mImage->setImage(path);
}
void GridTileComponent::setImage(const std::shared_ptr<TextureResource>& texture)
{
mImage->setImage(texture);
// Resize now to prevent flickering images when scrolling
resize();
}
void GridTileComponent::setSelected(bool selected)
{
mSelected = selected;
}
void GridTileComponent::setVisible(bool visible)
{
mVisible = visible;
}
void GridTileComponent::resize()
{
const GridTileProperties& currentProperties = getCurrentProperties();
mImage->setMaxSize(currentProperties.mSize - currentProperties.mPadding);
mBackground.fitTo(currentProperties.mSize - Vector2f(32.0f, 32.0f)); // (32f, 32f) the NinePatchComponent natural padding
mBackground.setPosition(getSize().x() / 2, getSize().y() / 2);
}
const GridTileProperties& GridTileComponent::getCurrentProperties() const
{
return mSelected ? mSelectedProperties : mDefaultProperties;
}

View file

@ -0,0 +1,51 @@
#pragma once
#ifndef ES_CORE_COMPONENTS_GRID_TILE_COMPONENT_H
#define ES_CORE_COMPONENTS_GRID_TILE_COMPONENT_H
#include "NinePatchComponent.h"
#include "ImageComponent.h"
struct GridTileProperties
{
Vector2f mSize;
Vector2f mPadding;
std::string mBackgroundImage;
unsigned int mImageColor;
unsigned int mBackgroundColor;
};
class GridTileComponent : public GuiComponent
{
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
// to calculate the grid dimension before it instantiate the GridTileComponents
static Vector2f getDefaultTileSize();
Vector2f getSelectedTileSize() const;
bool isSelected() const;
void setImage(const std::string& path);
void setImage(const std::shared_ptr<TextureResource>& texture);
void setSelected(bool selected);
void setVisible(bool visible);
private:
void resize();
const GridTileProperties& getCurrentProperties() const;
std::shared_ptr<ImageComponent> mImage;
NinePatchComponent mBackground;
GridTileProperties mDefaultProperties;
GridTileProperties mSelectedProperties;
bool mSelected;
bool mVisible;
};
#endif // ES_CORE_COMPONENTS_GRID_TILE_COMPONENT_H

View file

@ -4,6 +4,7 @@
#include "components/IList.h"
#include "resources/TextureResource.h"
#include "GridTileComponent.h"
struct ImageGridData
{
@ -43,15 +44,17 @@ public:
inline void setCursorChangedCallback(const std::function<void(CursorState state)>& func) { mCursorChangedCallback = func; }
private:
// Calculate how much tiles of size mTileMaxSize we can fit in a grid of size mSize using a margin of size mMargin
Vector2i getGridDimension() const
// Calculate how much tiles of size mTileSize we can fit in a grid of size mSize using a margin of size mMargin
void calcGridDimension()
{
// GRID_SIZE = COLUMNS * TILE_SIZE + (COLUMNS - 1) * MARGIN
// <=> COLUMNS = (GRID_SIZE + MARGIN) / (TILE_SIZE + MARGIN)
return Vector2i((int) ((mSize.x() + mMargin.x()) / (mTileMaxSize.x() + mMargin.x())),
(int) ((mSize.y() + mMargin.y()) / (mTileMaxSize.y() + mMargin.y())));
// GRID_SIZE = COLUMNS * TILE_SIZE + (COLUMNS - 1) * MARGIN
// <=> COLUMNS = (GRID_SIZE + MARGIN) / (TILE_SIZE + MARGIN)
Vector2f gridDimension = (mSize + mMargin) / (mTileSize + mMargin);
mGridDimension = Vector2i(gridDimension.x(), gridDimension.y());
};
int getStartPosition();
void buildImages();
void updateImages();
@ -62,10 +65,12 @@ private:
bool mEntriesDirty;
Vector2f mMargin;
Vector2f mTileMaxSize;
Vector2f mSelectedTileMaxSize;
Vector2f mTileSize;
Vector2i mGridDimension;
std::vector<ImageComponent> mImages;
std::vector< std::shared_ptr<GridTileComponent> > mTiles;
std::shared_ptr<ThemeData> mTheme;
};
template<typename T>
@ -75,10 +80,11 @@ ImageGridComponent<T>::ImageGridComponent(Window* window) : IList<ImageGridData,
mEntriesDirty = true;
mSize = screen * 0.79f;
mMargin = screen * 0.01f;
mTileMaxSize = screen * 0.19f;
mSelectedTileMaxSize = mTileMaxSize * 1.15f;
mSize = screen * 0.80f;
mMargin = screen * 0.07f;
mTileSize = GridTileComponent::getDefaultTileSize();
calcGridDimension();
}
template<typename T>
@ -109,7 +115,7 @@ bool ImageGridComponent<T>::input(InputConfig* config, Input input)
if(dir != Vector2i::Zero())
{
listInput(dir.x() + dir.y() * getGridDimension().x());
listInput(dir.x() + dir.y() * mGridDimension.x());
return true;
}
}else{
@ -126,6 +132,9 @@ template<typename T>
void ImageGridComponent<T>::update(int deltaTime)
{
listUpdate(deltaTime);
for(auto it = mTiles.begin(); it != mTiles.end(); it++)
(*it)->update();
}
template<typename T>
@ -140,36 +149,20 @@ void ImageGridComponent<T>::render(const Transform4x4f& parentTrans)
mEntriesDirty = false;
}
// Dirty solution (took from updateImages function) to keep the selected image and render it later (on top of the others)
// Will be changed for a cleaner way with the introduction of GridTileComponent
Vector2i gridDimension = getGridDimension();
int cursorRow = mCursor / gridDimension.x();
int start = (cursorRow - (gridDimension.y() / 2)) * gridDimension.x();
//if we're at the end put the row as close as we can and no higher
if(start + (gridDimension.x() * gridDimension.y()) >= (int)mEntries.size())
start = gridDimension.x() * ((int)mEntries.size()/gridDimension.x() - gridDimension.y() + 1);
if(start < 0)
start = 0;
unsigned int i = (unsigned int)start;
ImageComponent* selectedImage = NULL;
for(auto it = mImages.begin(); it != mImages.end(); it++)
std::shared_ptr<GridTileComponent> selectedTile = NULL;
for(auto it = mTiles.begin(); it != mTiles.end(); it++)
{
// If it's the selected image, keep it for later, otherwise render it now
if(i == (unsigned int)mCursor)
selectedImage = it.base();
std::shared_ptr<GridTileComponent> tile = (*it);
if(tile->isSelected())
selectedTile = tile;
else
it->render(trans);
i++;
tile->render(trans);
}
// Render the selected image on top of the others
if (selectedImage != NULL)
selectedImage->render(trans);
if (selectedTile != NULL)
selectedTile->render(trans);
GuiComponent::renderChildren(trans);
}
@ -178,6 +171,27 @@ template<typename T>
void ImageGridComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties)
{
GuiComponent::applyTheme(theme, view, element, properties);
// Keep the theme pointer to apply it on the tiles later on
mTheme = theme;
Vector2f screen = Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "imagegrid");
if (elem && elem->has("margin"))
mMargin = elem->get<Vector2f>("margin") * screen;
// We still need to manually get the grid tile size here,
// so we can recalculate the new grid dimension, and THEN (re)build the tiles
elem = theme->getElement(view, "default", "gridtile");
mTileSize = elem && elem->has("size") ?
elem->get<Vector2f>("size") * screen :
GridTileComponent::getDefaultTileSize();
// Recalculate grid dimension after theme changed
calcGridDimension();
}
template<typename T>
@ -196,77 +210,83 @@ void ImageGridComponent<T>::onSizeChanged()
updateImages();
}
// Create and position imagecomponents (mImages)
// Create and position tiles (mTiles)
template<typename T>
void ImageGridComponent<T>::buildImages()
{
mImages.clear();
mTiles.clear();
Vector2i gridDimension = getGridDimension();
Vector2f startPosition = mTileMaxSize / 2;
Vector2f tileDistance = mTileMaxSize + mMargin;
Vector2f startPosition = mTileSize / 2;
Vector2f tileDistance = mTileSize + mMargin;
// Layout tile size and position
for(int y = 0; y < gridDimension.y(); y++)
for(int y = 0; y < mGridDimension.y(); y++)
{
for(int x = 0; x < gridDimension.x(); x++)
for(int x = 0; x < mGridDimension.x(); x++)
{
// Create tiles
mImages.push_back(ImageComponent(mWindow));
ImageComponent& image = mImages.at(y * gridDimension.x() + x);
auto tile = std::make_shared<GridTileComponent>(mWindow);
image.setPosition(x * tileDistance.x() + startPosition.x(), y * tileDistance.y() + startPosition.y());
image.setOrigin(0.5f, 0.5f);
image.setMaxSize(mTileMaxSize);
image.setImage("");
tile->setPosition(x * tileDistance.x() + startPosition.x(), y * tileDistance.y() + startPosition.y());
tile->setOrigin(0.5f, 0.5f);
tile->setImage("");
if (mTheme)
tile->applyTheme(mTheme, "grid", "gridtile", ThemeFlags::ALL);
mTiles.push_back(tile);
}
}
}
// Return the starting position (the number of the game which will be displayed on top left of the screen)
template<typename T>
void ImageGridComponent<T>::updateImages()
int ImageGridComponent<T>::getStartPosition()
{
if(mImages.empty())
buildImages();
int cursorRow = mCursor / mGridDimension.x();
Vector2i gridDimension = getGridDimension();
int cursorRow = mCursor / gridDimension.x();
int start = (cursorRow - (gridDimension.y() / 2)) * gridDimension.x();
int start = (cursorRow - (mGridDimension.y() / 2)) * mGridDimension.x();
// 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)
if(start + (gridDimension.x() * gridDimension.y()) >= (int)mEntries.size())
start = gridDimension.x() * (((int)mEntries.size() - 1) / gridDimension.x() - gridDimension.y() + 1);
if(start + (mGridDimension.x() * mGridDimension.y()) >= (int)mEntries.size())
start = mGridDimension.x() * (((int)mEntries.size() - 1) / mGridDimension.x() - mGridDimension.y() + 1);
if(start < 0)
start = 0;
unsigned int i = (unsigned int)start;
for(unsigned int img = 0; img < mImages.size(); img++)
return start;
}
template<typename T>
void ImageGridComponent<T>::updateImages()
{
if(mTiles.empty())
buildImages();
int pos = getStartPosition();
for(int img = 0; img < mTiles.size(); img++)
{
ImageComponent& image = mImages.at(img);
if(i >= (unsigned int)size())
std::shared_ptr<GridTileComponent> tile = mTiles.at(img);
// If we have more tiles than we have to display images on screen, hide them
if(pos >= size())
{
image.setImage("");
tile->setSelected(false);
tile->setImage("");
tile->setVisible(false);
continue;
}
if(i == (unsigned int)mCursor)
{
image.setColorShift(0xFFFFFFFF);
image.setMaxSize(mSelectedTileMaxSize);
}else{
image.setColorShift(0xAAAAAABB);
image.setMaxSize(mTileMaxSize);
}
tile->setSelected(pos == mCursor);
tile->setImage(mEntries.at(pos).data.texture);
tile->setVisible(true);
image.setImage(mEntries.at(i).data.texture);
i++;
pos++;
}
}