From a006650c1c9eeb2b52aa45ed94c80c7c5b0dde4e Mon Sep 17 00:00:00 2001 From: Benjamin D Date: Thu, 22 Mar 2018 13:52:13 +0100 Subject: [PATCH] Add metadata from the detailed view to the grid view - Add the metadata from the detailed view to the grid view (minus the image, as it doesn't make sens in the grid view) - Add a callback to the ImageGridComponent to update the metadata info panel when cursor changed --- THEMES.md | 33 +++ .../src/views/gamelist/GridGameListView.cpp | 244 +++++++++++++++++- es-app/src/views/gamelist/GridGameListView.h | 28 +- es-core/src/components/ImageGridComponent.h | 9 +- 4 files changed, 310 insertions(+), 4 deletions(-) diff --git a/THEMES.md b/THEMES.md index 9f612d199..ccb620bc8 100644 --- a/THEMES.md +++ b/THEMES.md @@ -472,6 +472,39 @@ 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. +* Metadata + * Labels + * `text name="md_lbl_rating"` - ALL + * `text name="md_lbl_releasedate"` - ALL + * `text name="md_lbl_developer"` - ALL + * `text name="md_lbl_publisher"` - ALL + * `text name="md_lbl_genre"` - ALL + * `text name="md_lbl_players"` - ALL + * `text name="md_lbl_lastplayed"` - ALL + * `text name="md_lbl_playcount"` - ALL + + * Values + * All values will follow to the right of their labels if a position isn't specified. + + * `rating name="md_rating"` - ALL + - The "rating" metadata. + * `datetime name="md_releasedate"` - ALL + - The "releasedate" metadata. + * `text name="md_developer"` - ALL + - The "developer" metadata. + * `text name="md_publisher"` - ALL + - The "publisher" metadata. + * `text name="md_genre"` - ALL + - The "genre" metadata. + * `text name="md_players"` - ALL + - The "players" metadata (number of players the game supports). + * `datetime name="md_lastplayed"` - ALL + - The "lastplayed" metadata. Displayed as a string representing the time relative to "now" (e.g. "3 hours ago"). + * `text name="md_playcount"` - ALL + - The "playcount" metadata (number of times the game has been played). + * `text name="md_description"` - POSITION | SIZE | FONT_PATH | FONT_SIZE | COLOR | Z_INDEX + - Text is the "desc" metadata. If no `pos`/`size` is specified, will move and resize to fit under the lowest label and reach to the bottom of the screen. + --- #### system diff --git a/es-app/src/views/gamelist/GridGameListView.cpp b/es-app/src/views/gamelist/GridGameListView.cpp index 9314905dd..4c802959e 100644 --- a/es-app/src/views/gamelist/GridGameListView.cpp +++ b/es-app/src/views/gamelist/GridGameListView.cpp @@ -1,19 +1,73 @@ #include "views/gamelist/GridGameListView.h" +#include "animations/LambdaAnimation.h" #include "views/UIModeController.h" #include "views/ViewController.h" #include "CollectionSystemManager.h" #include "Settings.h" #include "SystemData.h" -GridGameListView::GridGameListView(Window* window, FileData* root) : ISimpleGameListView(window, root), - mGrid(window) +GridGameListView::GridGameListView(Window* window, FileData* root) : + ISimpleGameListView(window, root), + mGrid(window), + mDescContainer(window), mDescription(window), + + mLblRating(window), mLblReleaseDate(window), mLblDeveloper(window), mLblPublisher(window), + mLblGenre(window), mLblPlayers(window), mLblLastPlayed(window), mLblPlayCount(window), + + mRating(window), mReleaseDate(window), mDeveloper(window), mPublisher(window), + mGenre(window), mPlayers(window), mLastPlayed(window), mPlayCount(window) { + const float padding = 0.01f; + mGrid.setPosition(mSize.x() * 0.1f, mSize.y() * 0.1f); // mGrid.setSize(mSize.x(), mSize.y() * 0.8f); + mGrid.setCursorChangedCallback([&](const CursorState& /*state*/) { updateInfoPanel(); }); addChild(&mGrid); populateList(root->getChildrenListToDisplay()); + + // metadata labels + values + mLblRating.setText("Rating: "); + addChild(&mLblRating); + addChild(&mRating); + mLblReleaseDate.setText("Released: "); + addChild(&mLblReleaseDate); + addChild(&mReleaseDate); + mLblDeveloper.setText("Developer: "); + addChild(&mLblDeveloper); + addChild(&mDeveloper); + mLblPublisher.setText("Publisher: "); + addChild(&mLblPublisher); + addChild(&mPublisher); + mLblGenre.setText("Genre: "); + addChild(&mLblGenre); + addChild(&mGenre); + mLblPlayers.setText("Players: "); + addChild(&mLblPlayers); + addChild(&mPlayers); + mLblLastPlayed.setText("Last played: "); + addChild(&mLblLastPlayed); + mLastPlayed.setDisplayMode(DateTimeComponent::DISP_RELATIVE_TO_NOW); + addChild(&mLastPlayed); + mLblPlayCount.setText("Times played: "); + addChild(&mLblPlayCount); + addChild(&mPlayCount); + + mDescContainer.setPosition(mSize.x() * padding, mSize.y() * 0.65f); + mDescContainer.setSize(mSize.x() * (0.50f - 2*padding), mSize.y() - mDescContainer.getPosition().y()); + mDescContainer.setAutoScroll(true); + mDescContainer.setDefaultZIndex(40); + addChild(&mDescContainer); + + mDescription.setFont(Font::get(FONT_SIZE_SMALL)); + mDescription.setSize(mDescContainer.getSize().x(), 0); + mDescContainer.addChild(&mDescription); + + + initMDLabels(); + initMDValues(); + updateInfoPanel(); } FileData* GridGameListView::getCursor() @@ -65,6 +119,164 @@ void GridGameListView::populateList(const std::vector& files) } } +void GridGameListView::onThemeChanged(const std::shared_ptr& theme) +{ + ISimpleGameListView::onThemeChanged(theme); + + using namespace ThemeFlags; + + initMDLabels(); + std::vector labels = getMDLabels(); + assert(labels.size() == 8); + const char* lblElements[8] = { + "md_lbl_rating", "md_lbl_releasedate", "md_lbl_developer", "md_lbl_publisher", + "md_lbl_genre", "md_lbl_players", "md_lbl_lastplayed", "md_lbl_playcount" + }; + + for(unsigned int i = 0; i < labels.size(); i++) + { + labels[i]->applyTheme(theme, getName(), lblElements[i], ALL); + } + + + initMDValues(); + std::vector values = getMDValues(); + assert(values.size() == 8); + const char* valElements[8] = { + "md_rating", "md_releasedate", "md_developer", "md_publisher", + "md_genre", "md_players", "md_lastplayed", "md_playcount" + }; + + for(unsigned int i = 0; i < values.size(); i++) + { + values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT); + } + + mDescContainer.applyTheme(theme, getName(), "md_description", POSITION | ThemeFlags::SIZE | Z_INDEX); + mDescription.setSize(mDescContainer.getSize().x(), 0); + mDescription.applyTheme(theme, getName(), "md_description", ALL ^ (POSITION | ThemeFlags::SIZE | ThemeFlags::ORIGIN | TEXT | ROTATION)); + + sortChildren(); +} + +void GridGameListView::initMDLabels() +{ + std::vector components = getMDLabels(); + + const unsigned int colCount = 2; + const unsigned int rowCount = (int)(components.size() / 2); + + Vector3f start(mSize.x() * 0.01f, mSize.y() * 0.625f, 0.0f); + + const float colSize = (mSize.x() * 0.48f) / colCount; + const float rowPadding = 0.01f * mSize.y(); + + for(unsigned int i = 0; i < components.size(); i++) + { + const unsigned int row = i % rowCount; + Vector3f pos(0.0f, 0.0f, 0.0f); + if(row == 0) + { + pos = start + Vector3f(colSize * (i / rowCount), 0, 0); + }else{ + // work from the last component + GuiComponent* lc = components[i-1]; + pos = lc->getPosition() + Vector3f(0, lc->getSize().y() + rowPadding, 0); + } + + components[i]->setFont(Font::get(FONT_SIZE_SMALL)); + components[i]->setPosition(pos); + components[i]->setDefaultZIndex(40); + } +} + +void GridGameListView::initMDValues() +{ + std::vector labels = getMDLabels(); + std::vector values = getMDValues(); + + std::shared_ptr defaultFont = Font::get(FONT_SIZE_SMALL); + mRating.setSize(defaultFont->getHeight() * 5.0f, (float)defaultFont->getHeight()); + mReleaseDate.setFont(defaultFont); + mDeveloper.setFont(defaultFont); + mPublisher.setFont(defaultFont); + mGenre.setFont(defaultFont); + mPlayers.setFont(defaultFont); + mLastPlayed.setFont(defaultFont); + mPlayCount.setFont(defaultFont); + + float bottom = 0.0f; + + const float colSize = (mSize.x() * 0.48f) / 2; + for(unsigned int i = 0; i < labels.size(); i++) + { + const float heightDiff = (labels[i]->getSize().y() - values[i]->getSize().y()) / 2; + values[i]->setPosition(labels[i]->getPosition() + Vector3f(labels[i]->getSize().x(), heightDiff, 0)); + values[i]->setSize(colSize - labels[i]->getSize().x(), values[i]->getSize().y()); + values[i]->setDefaultZIndex(40); + + float testBot = values[i]->getPosition().y() + values[i]->getSize().y(); + if(testBot > bottom) + bottom = testBot; + } + + mDescContainer.setPosition(mDescContainer.getPosition().x(), bottom + mSize.y() * 0.01f); + mDescContainer.setSize(mDescContainer.getSize().x(), mSize.y() - mDescContainer.getPosition().y()); +} + +void GridGameListView::updateInfoPanel() +{ + FileData* file = (mGrid.size() == 0 || mGrid.isScrolling()) ? NULL : mGrid.getSelected(); + + bool fadingOut; + if(file == NULL) + { + //mDescription.setText(""); + fadingOut = true; + }else{ + mDescription.setText(file->metadata.get("desc")); + mDescContainer.reset(); + + mRating.setValue(file->metadata.get("rating")); + mReleaseDate.setValue(file->metadata.get("releasedate")); + mDeveloper.setValue(file->metadata.get("developer")); + mPublisher.setValue(file->metadata.get("publisher")); + mGenre.setValue(file->metadata.get("genre")); + mPlayers.setValue(file->metadata.get("players")); + + if(file->getType() == GAME) + { + mLastPlayed.setValue(file->metadata.get("lastplayed")); + mPlayCount.setValue(file->metadata.get("playcount")); + } + + fadingOut = false; + } + + std::vector comps = getMDValues(); + comps.push_back(&mDescription); + std::vector labels = getMDLabels(); + comps.insert(comps.cend(), labels.cbegin(), labels.cend()); + + for(auto it = comps.cbegin(); it != comps.cend(); it++) + { + GuiComponent* comp = *it; + // an animation is playing + // then animate if reverse != fadingOut + // an animation is not playing + // then animate if opacity != our target opacity + if((comp->isAnimationPlaying(0) && comp->isAnimationReversed(0) != fadingOut) || + (!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0 : 255))) + { + auto func = [comp](float t) + { + comp->setOpacity((unsigned char)(Math::lerp(0.0f, 1.0f, t)*255)); + }; + comp->setAnimation(new LambdaAnimation(func, 150), 0, nullptr, fadingOut); + } + } +} + void GridGameListView::addPlaceholder() { // empty grid - add a placeholder @@ -106,6 +318,34 @@ void GridGameListView::remove(FileData *game, bool deleteFile) onFileChanged(parent, FILE_REMOVED); // update the view, with game removed } +std::vector GridGameListView::getMDLabels() +{ + std::vector ret; + ret.push_back(&mLblRating); + ret.push_back(&mLblReleaseDate); + ret.push_back(&mLblDeveloper); + ret.push_back(&mLblPublisher); + ret.push_back(&mLblGenre); + ret.push_back(&mLblPlayers); + ret.push_back(&mLblLastPlayed); + ret.push_back(&mLblPlayCount); + return ret; +} + +std::vector GridGameListView::getMDValues() +{ + std::vector ret; + ret.push_back(&mRating); + ret.push_back(&mReleaseDate); + ret.push_back(&mDeveloper); + ret.push_back(&mPublisher); + ret.push_back(&mGenre); + ret.push_back(&mPlayers); + ret.push_back(&mLastPlayed); + ret.push_back(&mPlayCount); + return ret; +} + std::vector GridGameListView::getHelpPrompts() { std::vector prompts; diff --git a/es-app/src/views/gamelist/GridGameListView.h b/es-app/src/views/gamelist/GridGameListView.h index 0fb7fc145..7fd8b5f2f 100644 --- a/es-app/src/views/gamelist/GridGameListView.h +++ b/es-app/src/views/gamelist/GridGameListView.h @@ -2,6 +2,9 @@ #ifndef ES_APP_VIEWS_GAME_LIST_GRID_GAME_LIST_VIEW_H #define ES_APP_VIEWS_GAME_LIST_GRID_GAME_LIST_VIEW_H +#include "components/DateTimeComponent.h" +#include "components/RatingComponent.h" +#include "components/ScrollableContainer.h" #include "components/ImageGridComponent.h" #include "views/gamelist/ISimpleGameListView.h" @@ -10,7 +13,7 @@ class GridGameListView : public ISimpleGameListView public: GridGameListView(Window* window, FileData* root); - //virtual void onThemeChanged(const std::shared_ptr& theme) override; + virtual void onThemeChanged(const std::shared_ptr& theme) override; virtual FileData* getCursor() override; virtual void setCursor(FileData*) override; @@ -30,6 +33,29 @@ protected: virtual void addPlaceholder(); ImageGridComponent mGrid; + +private: + void updateInfoPanel(); + + void initMDLabels(); + void initMDValues(); + + TextComponent mLblRating, mLblReleaseDate, mLblDeveloper, mLblPublisher, mLblGenre, mLblPlayers, mLblLastPlayed, mLblPlayCount; + + RatingComponent mRating; + DateTimeComponent mReleaseDate; + TextComponent mDeveloper; + TextComponent mPublisher; + TextComponent mGenre; + TextComponent mPlayers; + DateTimeComponent mLastPlayed; + TextComponent mPlayCount; + + std::vector getMDLabels(); + std::vector getMDValues(); + + ScrollableContainer mDescContainer; + TextComponent mDescription; }; #endif // ES_APP_VIEWS_GAME_LIST_GRID_GAME_LIST_VIEW_H diff --git a/es-core/src/components/ImageGridComponent.h b/es-core/src/components/ImageGridComponent.h index 95aed4bbf..6acdb1073 100644 --- a/es-core/src/components/ImageGridComponent.h +++ b/es-core/src/components/ImageGridComponent.h @@ -39,6 +39,8 @@ public: void update(int deltaTime) override; void render(const Transform4x4f& parentTrans) override; + 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 @@ -54,6 +56,8 @@ private: virtual void onCursorChanged(const CursorState& state); + std::function mCursorChangedCallback; + bool mEntriesDirty; Vector2f mMargin; @@ -170,9 +174,12 @@ void ImageGridComponent::render(const Transform4x4f& parentTrans) } template -void ImageGridComponent::onCursorChanged(const CursorState& /*state*/) +void ImageGridComponent::onCursorChanged(const CursorState& state) { updateImages(); + + if(mCursorChangedCallback) + mCursorChangedCallback(state); } template