diff --git a/CMakeLists.txt b/CMakeLists.txt index 057868be7..58678f012 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,6 +164,7 @@ set(ES_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentListComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageGridComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/NinePatchComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/OptionListComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/RatingComponent.h @@ -195,6 +196,7 @@ set(ES_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/views/BasicGameListView.h ${CMAKE_CURRENT_SOURCE_DIR}/src/views/DetailedGameListView.h ${CMAKE_CURRENT_SOURCE_DIR}/src/views/GameListView.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/GridGameListView.h ${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.h ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Font.h @@ -264,6 +266,7 @@ set(ES_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/views/BasicGameListView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/views/DetailedGameListView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/views/GameListView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/GridGameListView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.cpp ${CMAKE_CURRENT_SOURCE_DIR}/data/ResourceUtil.cpp diff --git a/src/components/GuiGameScraper.cpp b/src/components/GuiGameScraper.cpp index fbae61f38..ed804338d 100644 --- a/src/components/GuiGameScraper.cpp +++ b/src/components/GuiGameScraper.cpp @@ -123,6 +123,7 @@ void GuiGameScraper::onSearchDone(std::vector results) mList.resetCursor(); mList.moveCursor(Eigen::Vector2i(0, 1)); //move cursor to first game if there is one + updateInfoPane(); } int GuiGameScraper::getSelectedIndex() @@ -161,22 +162,7 @@ bool GuiGameScraper::input(InputConfig* config, Input input) if(config->isMappedTo("up", input) || config->isMappedTo("down", input) && input.value != 0) { - //update game info pane - int i = getSelectedIndex(); - if(i != -1) - { - mResultName.setText(mScraperResults.at(i).get("name")); - mResultDesc.setText(mScraperResults.at(i).get("desc")); - mResultInfo.setScrollPos(Eigen::Vector2d(0, 0)); - mResultInfo.resetAutoScrollTimer(); - - std::string thumb = mScraperResults.at(i).get("thumbnail"); - mResultThumbnail.setImage(""); - if(!thumb.empty()) - mThumbnailReq = std::unique_ptr(new HttpReq(thumb)); - else - mThumbnailReq.reset(); - } + updateInfoPane(); } //stopped editing @@ -189,6 +175,25 @@ bool GuiGameScraper::input(InputConfig* config, Input input) return ret; } +void GuiGameScraper::updateInfoPane() +{ + int i = getSelectedIndex(); + if(i != -1) + { + mResultName.setText(mScraperResults.at(i).get("name")); + mResultDesc.setText(mScraperResults.at(i).get("desc")); + mResultInfo.setScrollPos(Eigen::Vector2d(0, 0)); + mResultInfo.resetAutoScrollTimer(); + + std::string thumb = mScraperResults.at(i).get("thumbnail"); + mResultThumbnail.setImage(""); + if(!thumb.empty()) + mThumbnailReq = std::unique_ptr(new HttpReq(thumb)); + else + mThumbnailReq.reset(); + } +} + void GuiGameScraper::update(int deltaTime) { if(mThumbnailReq && mThumbnailReq->status() != HttpReq::REQ_IN_PROGRESS) diff --git a/src/components/GuiGameScraper.h b/src/components/GuiGameScraper.h index 1a268a114..49925fd40 100644 --- a/src/components/GuiGameScraper.h +++ b/src/components/GuiGameScraper.h @@ -25,6 +25,7 @@ public: private: int getSelectedIndex(); void onSearchDone(std::vector results); + void updateInfoPane(); void updateThumbnail(); ComponentListComponent mList; diff --git a/src/components/ImageGridComponent.h b/src/components/ImageGridComponent.h new file mode 100644 index 000000000..240330c0b --- /dev/null +++ b/src/components/ImageGridComponent.h @@ -0,0 +1,360 @@ +#pragma once + +#include "../GuiComponent.h" +#include "../components/ImageComponent.h" +#include "../Log.h" + +template +class ImageGridComponent : public GuiComponent +{ +public: + ImageGridComponent(Window* window); + + struct Entry + { + std::shared_ptr texture; + T object; + + Entry() {} + Entry(std::shared_ptr t, const T& o) : texture(t), object(o) {} + }; + + void add(const std::string& imagePath, const T& obj); + void remove(const T& obj); + void clear(); + + void setCursor(const T& select); + + inline const T& getSelected() const { return mEntries.at(mCursor).object; } + inline const std::vector& getList() const { return mEntries; } + + enum CursorState { + CURSOR_STOPPED, + CURSOR_SCROLLING + }; + + void stopScrolling(); + + void onSizeChanged() override; + + bool input(InputConfig* config, Input input) override; + void update(int deltaTime) override; + void render(const Eigen::Affine3f& parentTrans) override; + +private: + Eigen::Vector2f getSquareSize(std::shared_ptr tex = nullptr) const + { + Eigen::Vector2f aspect(1, 1); + + if(tex) + { + const Eigen::Vector2i& texSize = tex->getSize(); + + if(texSize.x() > texSize.y()) + aspect[0] = (float)texSize.x() / texSize.y(); + else + aspect[1] = (float)texSize.y() / texSize.x(); + } + + return Eigen::Vector2f(156 * aspect.x(), 156 * aspect.y()); + }; + + Eigen::Vector2f getMaxSquareSize() const + { + Eigen::Vector2f squareSize(32, 32); + + // calc biggest square size + for(auto it = mEntries.begin(); it != mEntries.end(); it++) + { + Eigen::Vector2f chkSize = getSquareSize(it->texture); + if(chkSize.x() > squareSize.x()) + squareSize[0] = chkSize[0]; + if(chkSize.y() > squareSize.y()) + squareSize[1] = chkSize[1]; + } + + return squareSize; + }; + + Eigen::Vector2i getGridSize() const + { + Eigen::Vector2f squareSize = getMaxSquareSize(); + Eigen::Vector2i gridSize(mSize.x() / (squareSize.x() + getPadding().x()), mSize.y() / (squareSize.y() + getPadding().y())); + return gridSize; + }; + + Eigen::Vector2f getPadding() const { return Eigen::Vector2f(24, 24); } + + void buildImages(); + void updateImages(); + + static const int SCROLL_DELAY = 507; + static const int SCROLL_TIME = 150; + + void setScrollDir(Eigen::Vector2i dir); + void scroll(); + void onCursorChanged(CursorState state); + + int mCursor; + + Eigen::Vector2i mScrollDir; + int mScrollAccumulator; + + bool mEntriesDirty; + + std::vector mEntries; + std::vector mImages; +}; + +template +ImageGridComponent::ImageGridComponent(Window* window) : GuiComponent(window) +{ + mEntriesDirty = true; + mCursor = 0; + mScrollDir << 0, 0; + mScrollAccumulator = 0; +} + +template +void ImageGridComponent::add(const std::string& imagePath, const T& obj) +{ + Entry e(ResourceManager::getInstance()->fileExists(imagePath) ? TextureResource::get(imagePath) : TextureResource::get(":/button.png"), obj); + mEntries.push_back(e); + mEntriesDirty = true; +} + +template +void ImageGridComponent::remove(const T& obj) +{ + for(auto it = mEntries.begin(); it != mEntries.end(); it++) + { + if((*it).object == obj) + { + if(mCursor > 0 && it - mRowVector.begin() >= mCursor) + { + mCursor--; + onCursorChanged(CURSOR_STOPPED); + } + + mEntriesDirty = true; + mEntries.erase(it); + return; + } + } + + LOG(LogError) << "Tried to remove an object we couldn't find"; +} + +template +void ImageGridComponent::clear() +{ + mEntries.clear(); + mCursor = 0; + mScrollDir << 0, 0; + onCursorChanged(CURSOR_STOPPED); + mEntriesDirty = true; +} + +template +void ImageGridComponent::setCursor(const T& obj) +{ + for(auto it = mEntries.begin(); it != mEntries.end(); it++) + { + if((*it).object == obj) + { + mCursor = it - mEntries.begin(); + onCursorChanged(CURSOR_STOPPED); + return; + } + } + + LOG(LogError) << "Tried to set cursor to object we couldn't find"; +} + +template +void ImageGridComponent::stopScrolling() +{ + mScrollDir = Eigen::Vector2i::Zero(); +} + +template +void ImageGridComponent::scroll() +{ + if(mEntries.size() == 0) + return; + + int offset = 0; + Eigen::Vector2i size = getGridSize(); + + offset += mScrollDir.x(); + offset += mScrollDir.y() * size.x(); + + mCursor += offset; + if(mCursor < 0) + mCursor += mEntries.size(); + if(mCursor >= (int)mEntries.size()) + mCursor -= mEntries.size(); + + onCursorChanged(CURSOR_SCROLLING); +} + +template +void ImageGridComponent::setScrollDir(Eigen::Vector2i dir) +{ + mScrollDir = dir; + mScrollAccumulator = -SCROLL_DELAY; +} + +template +bool ImageGridComponent::input(InputConfig* config, Input input) +{ + if(input.value != 0) + { + Eigen::Vector2i dir = Eigen::Vector2i::Zero(); + if(config->isMappedTo("up", input)) + dir[1] = -1; + else if(config->isMappedTo("down", input)) + dir[1] = 1; + else if(config->isMappedTo("left", input)) + dir[0] = -1; + else if(config->isMappedTo("right", input)) + dir[0] = 1; + + if(dir != Eigen::Vector2i::Zero()) + { + setScrollDir(dir); + scroll(); + return true; + } + }else{ + if(config->isMappedTo("up", input) || config->isMappedTo("down", input) || config->isMappedTo("left", input) || config->isMappedTo("right", input)) + { + mScrollDir << 0, 0; + onCursorChanged(CURSOR_STOPPED); + } + } + + return GuiComponent::input(config, input); +} + +template +void ImageGridComponent::update(int deltaTime) +{ + if(mScrollDir != Eigen::Vector2i::Zero()) + { + mScrollAccumulator += deltaTime; + while(mScrollAccumulator >= SCROLL_TIME) + { + scroll(); + mScrollAccumulator -= SCROLL_TIME; + } + } +} + +template +void ImageGridComponent::render(const Eigen::Affine3f& parentTrans) +{ + Eigen::Affine3f trans = getTransform() * parentTrans; + + if(mEntriesDirty) + { + buildImages(); + updateImages(); + mEntriesDirty = false; + } + + for(auto it = mImages.begin(); it != mImages.end(); it++) + { + it->render(trans); + } + + renderChildren(trans); +} + +template +void ImageGridComponent::onCursorChanged(CursorState state) +{ + updateImages(); +} + +template +void ImageGridComponent::onSizeChanged() +{ + buildImages(); + updateImages(); +} + +// create and position imagecomponents (mImages) +template +void ImageGridComponent::buildImages() +{ + mImages.clear(); + + Eigen::Vector2i gridSize = getGridSize(); + Eigen::Vector2f squareSize = getMaxSquareSize(); + Eigen::Vector2f padding = getPadding(); + + // attempt to center within our size + Eigen::Vector2f totalSize(gridSize.x() * (squareSize.x() + padding.x()), gridSize.y() * (squareSize.y() + padding.y())); + Eigen::Vector2f offset(mSize.x() - totalSize.x(), mSize.y() - totalSize.y()); + offset /= 2; + + for(int y = 0; y < gridSize.y(); y++) + { + for(int x = 0; x < gridSize.x(); x++) + { + mImages.push_back(ImageComponent(mWindow)); + ImageComponent& image = mImages.at(y * gridSize.x() + x); + + image.setPosition((squareSize.x() + padding.x()) * (x + 0.5f) + offset.x(), (squareSize.y() + padding.y()) * (y + 0.5f) + offset.y()); + image.setOrigin(0.5f, 0.5f); + image.setResize(squareSize.x(), squareSize.y(), true); + image.setImage(""); + } + } +} + +template +void ImageGridComponent::updateImages() +{ + if(mImages.empty()) + buildImages(); + + Eigen::Vector2i gridSize = getGridSize(); + + int cursorRow = mCursor / gridSize.x(); + int cursorCol = mCursor % gridSize.x(); + + int start = (cursorRow - (gridSize.y() / 2)) * gridSize.x(); + + //if we're at the end put the row as close as we can and no higher + if(start + (gridSize.x() * gridSize.y()) >= (int)mEntries.size()) + start = gridSize.x() * ((int)mEntries.size()/gridSize.x() - gridSize.y() + 1); + + if(start < 0) + start = 0; + + unsigned int i = (unsigned int)start; + for(unsigned int img = 0; img < mImages.size(); img++) + { + ImageComponent& image = mImages.at(img); + if(i >= mEntries.size()) + { + image.setImage(""); + continue; + } + + Eigen::Vector2f squareSize = getSquareSize(mEntries.at(i).texture); + if(i == mCursor) + { + image.setColorShift(0xFFFFFFFF); + image.setResize(squareSize.x() + getPadding().x() * 0.95f, squareSize.y() + getPadding().y() * 0.95f, true); + }else{ + image.setColorShift(0xAAAAAABB); + image.setResize(squareSize.x(), squareSize.y(), true); + } + + image.setImage(mEntries.at(i).texture); + i++; + } +} diff --git a/src/views/GridGameListView.cpp b/src/views/GridGameListView.cpp new file mode 100644 index 000000000..095232a63 --- /dev/null +++ b/src/views/GridGameListView.cpp @@ -0,0 +1,90 @@ +#include "GridGameListView.h" +#include "../ThemeData.h" +#include "../Window.h" +#include "ViewController.h" + +GridGameListView::GridGameListView(Window* window, FileData* root) : GameListView(window, root), + mGrid(window), mBackground(window) +{ + mBackground.setResize(mSize.x(), mSize.y(), true); + addChild(&mBackground); + + mGrid.setPosition(0, mSize.y() * 0.2f); + mGrid.setSize(mSize.x(), mSize.y() * 0.8f); + addChild(&mGrid); + + populateList(root); +} + +void GridGameListView::onFileChanged(FileData* file, FileChangeType change) +{ + // fuck it, just completely repopulate always all the time + FileData* cursor = getCursor(); + populateList(cursor->getParent()); + mGrid.setCursor(cursor); +} + +void GridGameListView::onThemeChanged(const std::shared_ptr& theme) +{ + mBackground.setImage(theme->getImage("backgroundImage").getTexture()); + mBackground.setTiling(theme->getImage("backgroundImage").tile); +} + +FileData* GridGameListView::getCursor() +{ + return mGrid.getSelected(); +} + +void GridGameListView::setCursor(FileData* file) +{ + assert(file->getParent() == getCursor()->getParent()); // too lazy to implement right now + mGrid.setCursor(file); +} + +bool GridGameListView::input(InputConfig* config, Input input) +{ + if(config->isMappedTo("a", input)) + { + if(mGrid.getList().size() > 0) + { + FileData* cursor = getCursor(); + if(cursor->getType() == GAME) + { + mWindow->getViewController()->launch(cursor); + }else{ + // it's a folder + if(cursor->getChildren().size() > 0) + { + mCursorStack.push(cursor); + populateList(cursor); + } + } + + return true; + } + }else if(config->isMappedTo("b", input)) + { + if(mCursorStack.size()) + { + populateList(mCursorStack.top()->getParent()); + mGrid.setCursor(mCursorStack.top()); + mCursorStack.pop(); + mTheme->playSound("backSound"); + }else{ + mGrid.stopScrolling(); + mWindow->getViewController()->goToSystemSelect(); + } + + return true; + } + return GameListView::input(config, input); +} + +void GridGameListView::populateList(FileData* root) +{ + mGrid.clear(); + for(auto it = root->getChildren().begin(); it != root->getChildren().end(); it++) + { + mGrid.add((*it)->getThumbnailPath(), *it); + } +} diff --git a/src/views/GridGameListView.h b/src/views/GridGameListView.h new file mode 100644 index 000000000..f9c36afe0 --- /dev/null +++ b/src/views/GridGameListView.h @@ -0,0 +1,28 @@ +#pragma once + +#include "GameListView.h" +#include "../components/ImageGridComponent.h" +#include "../components/ImageComponent.h" +#include + +class GridGameListView : public GameListView +{ +public: + GridGameListView(Window* window, FileData* root); + + virtual void onFileChanged(FileData* file, FileChangeType change) override; + virtual void onThemeChanged(const std::shared_ptr& theme) override; + + FileData* getCursor() override; + void setCursor(FileData*) override; + + virtual bool input(InputConfig* config, Input input) override; + +private: + void populateList(FileData* root); + + ImageGridComponent mGrid; + ImageComponent mBackground; + + std::stack mCursorStack; +}; diff --git a/src/views/ViewController.cpp b/src/views/ViewController.cpp index 8a6e32fcb..94164bc34 100644 --- a/src/views/ViewController.cpp +++ b/src/views/ViewController.cpp @@ -4,6 +4,7 @@ #include "BasicGameListView.h" #include "DetailedGameListView.h" +#include "GridGameListView.h" ViewController::ViewController(Window* window) : GuiComponent(window), mCurrentView(nullptr), mCameraPos(Eigen::Affine3f::Identity()) @@ -113,11 +114,14 @@ std::shared_ptr ViewController::getSystemView(SystemData* system) break; } } - + if(detailed) view = std::shared_ptr(new DetailedGameListView(mWindow, system->getRootFolder())); else view = std::shared_ptr(new BasicGameListView(mWindow, system->getRootFolder())); + + + //view = std::shared_ptr(new GridGameListView(mWindow, system->getRootFolder())); view->setTheme(system->getTheme()); }else{