diff --git a/THEMES.md b/THEMES.md index 5bd183f56..edc0ab310 100644 --- a/THEMES.md +++ b/THEMES.md @@ -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 diff --git a/es-app/src/views/gamelist/GridGameListView.cpp b/es-app/src/views/gamelist/GridGameListView.cpp index 21dfc17c3..bc5e9e481 100644 --- a/es-app/src/views/gamelist/GridGameListView.cpp +++ b/es-app/src/views/gamelist/GridGameListView.cpp @@ -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); diff --git a/es-core/CMakeLists.txt b/es-core/CMakeLists.txt index 0e367c7bf..fb4dcbee8 100644 --- a/es-core/CMakeLists.txt +++ b/es-core/CMakeLists.txt @@ -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 diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 9df845a74..6b39f3a35 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -27,7 +27,14 @@ std::map> 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> 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) diff --git a/es-core/src/components/GridTileComponent.cpp b/es-core/src/components/GridTileComponent.cpp new file mode 100644 index 000000000..9e4f6f810 --- /dev/null +++ b/es-core/src/components/GridTileComponent.cpp @@ -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(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& 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("size") * screen; + + if (elem->has("padding")) + mDefaultProperties.mPadding = elem->get("padding"); + + if (elem->has("backgroundImage")) + mDefaultProperties.mBackgroundImage = elem->get("backgroundImage"); + + if (elem->has("imageColor")) + mDefaultProperties.mImageColor = elem->get("imageColor"); + + if (elem->has("backgroundColor")) + mDefaultProperties.mBackgroundColor = elem->get("backgroundColor"); + } + + elem = theme->getElement(view, "selected", "gridtile"); + + mSelectedProperties.mSize = elem && elem->has("size") ? + elem->get("size") * screen : + getSelectedTileSize(); + + mSelectedProperties.mPadding = elem && elem->has("padding") ? + elem->get("padding") : + mDefaultProperties.mPadding; + + mSelectedProperties.mBackgroundImage = elem && elem->has("backgroundImage") ? + elem->get("backgroundImage") : + mDefaultProperties.mBackgroundImage; + + if (elem && elem->has("imageColor")) + mSelectedProperties.mImageColor = elem->get("imageColor"); + + if (elem && elem->has("backgroundColor")) + mSelectedProperties.mBackgroundColor = elem->get("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& 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; +} \ No newline at end of file diff --git a/es-core/src/components/GridTileComponent.h b/es-core/src/components/GridTileComponent.h new file mode 100644 index 000000000..cdee62624 --- /dev/null +++ b/es-core/src/components/GridTileComponent.h @@ -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& 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& texture); + void setSelected(bool selected); + void setVisible(bool visible); + +private: + void resize(); + const GridTileProperties& getCurrentProperties() const; + + std::shared_ptr mImage; + NinePatchComponent mBackground; + + GridTileProperties mDefaultProperties; + GridTileProperties mSelectedProperties; + + bool mSelected; + bool mVisible; +}; + +#endif // ES_CORE_COMPONENTS_GRID_TILE_COMPONENT_H diff --git a/es-core/src/components/ImageGridComponent.h b/es-core/src/components/ImageGridComponent.h index 7cadd080e..68029eeec 100644 --- a/es-core/src/components/ImageGridComponent.h +++ b/es-core/src/components/ImageGridComponent.h @@ -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& 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 mImages; + std::vector< std::shared_ptr > mTiles; + + std::shared_ptr mTheme; }; template @@ -75,10 +80,11 @@ ImageGridComponent::ImageGridComponent(Window* window) : IList @@ -109,7 +115,7 @@ bool ImageGridComponent::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 void ImageGridComponent::update(int deltaTime) { listUpdate(deltaTime); + + for(auto it = mTiles.begin(); it != mTiles.end(); it++) + (*it)->update(); } template @@ -140,36 +149,20 @@ void ImageGridComponent::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 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 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 void ImageGridComponent::applyTheme(const std::shared_ptr& 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("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("size") * screen : + GridTileComponent::getDefaultTileSize(); + + // Recalculate grid dimension after theme changed + calcGridDimension(); } template @@ -196,77 +210,83 @@ void ImageGridComponent::onSizeChanged() updateImages(); } -// Create and position imagecomponents (mImages) +// Create and position tiles (mTiles) template void ImageGridComponent::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(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 -void ImageGridComponent::updateImages() +int ImageGridComponent::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 +void ImageGridComponent::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 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++; } }