From a3a4636fd57777eeda6d4e5a91b74043523dd7ec Mon Sep 17 00:00:00 2001 From: Aloshi Date: Fri, 20 Sep 2013 18:55:05 -0500 Subject: [PATCH] Search for box hooked up. Display thumbnails for results. Still need to resolve boxart. --- src/HttpReq.cpp | 33 ++++++++++++++- src/HttpReq.h | 4 ++ src/MetaData.cpp | 1 + src/components/ComponentListComponent.cpp | 15 ++++++- src/components/GuiGameScraper.cpp | 51 +++++++++++++++++++++-- src/components/GuiGameScraper.h | 7 ++++ src/components/ImageComponent.cpp | 10 +++++ src/components/ImageComponent.h | 1 + src/components/TextEditComponent.cpp | 7 ++++ src/components/TextEditComponent.h | 2 + src/resources/TextureResource.cpp | 28 +++++++++++++ src/resources/TextureResource.h | 1 + src/scrapers/GamesDBScraper.cpp | 13 +++++- 13 files changed, 166 insertions(+), 7 deletions(-) diff --git a/src/HttpReq.cpp b/src/HttpReq.cpp index 58c17bb5d..106192109 100644 --- a/src/HttpReq.cpp +++ b/src/HttpReq.cpp @@ -7,6 +7,38 @@ boost::asio::io_service HttpReq::io_service; HttpReq::HttpReq(const std::string& server, const std::string& path) : mResolver(io_service), mSocket(io_service), mStatus(REQ_IN_PROGRESS) +{ + start(server, path); +} + +HttpReq::HttpReq(const std::string& url) + : mResolver(io_service), mSocket(io_service), mStatus(REQ_IN_PROGRESS) +{ + size_t startpos = 0; + + if(url.substr(startpos, 7) == "http://") + startpos = 7; + else if(url.substr(0, 8) == "https://") + startpos = 8; + + if(url.substr(startpos, 4) == "www.") + startpos += 4; + + size_t pathStart = url.find('/', startpos); + std::string server = url.substr(startpos, pathStart - startpos); + std::string path = url.substr(pathStart, std::string::npos); + + start(server, path); +} + +HttpReq::~HttpReq() +{ + mResolver.cancel(); + mSocket.close(); + while(status() == REQ_IN_PROGRESS); //otherwise you get really weird heap-allocation-related crashes +} + +void HttpReq::start(const std::string& server, const std::string& path) { std::ostream req_str(&mRequest); req_str << "GET " << path << " HTTP/1.0\r\n"; @@ -21,7 +53,6 @@ HttpReq::HttpReq(const std::string& server, const std::string& path) boost::asio::placeholders::iterator)); } - void HttpReq::handleResolve(const boost::system::error_code& err, tcp::resolver::iterator endpoint_iterator) { if (!err) diff --git a/src/HttpReq.h b/src/HttpReq.h index ee8d2b1f7..31d368540 100644 --- a/src/HttpReq.h +++ b/src/HttpReq.h @@ -27,6 +27,9 @@ class HttpReq { public: HttpReq(const std::string& server, const std::string& path); + HttpReq(const std::string& url); + + ~HttpReq(); enum Status { @@ -47,6 +50,7 @@ public: private: static boost::asio::io_service io_service; + void start(const std::string& server, const std::string& path); void handleResolve(const boost::system::error_code& err, tcp::resolver::iterator endpoint_iterator); void handleConnect(const boost::system::error_code& err); void handleWriteRequest(const boost::system::error_code& err); diff --git a/src/MetaData.cpp b/src/MetaData.cpp index a9a5d02b2..0eba49ae3 100644 --- a/src/MetaData.cpp +++ b/src/MetaData.cpp @@ -19,6 +19,7 @@ std::vector MetaDataList::getDefaultGameMDD() {"name", MD_STRING, ""}, {"desc", MD_MULTILINE_STRING, ""}, {"image", MD_IMAGE_PATH, ""}, + {"thumbnail", MD_IMAGE_PATH, ""}, {"rating", MD_RATING, "0"}, {"userrating", MD_RATING, "0"}, {"playcount", MD_INT, "0"}, diff --git a/src/components/ComponentListComponent.cpp b/src/components/ComponentListComponent.cpp index 5321a0cbe..5b8bf4a12 100644 --- a/src/components/ComponentListComponent.cpp +++ b/src/components/ComponentListComponent.cpp @@ -104,6 +104,9 @@ void ComponentListComponent::removeEntriesIn(Eigen::Vector2i pos, Eigen::Vector2 if((*iter)->pos.x() >= pos.x() && (*iter)->pos.x() < pos.x() + size.x() && (*iter)->pos.y() >= pos.y() && (*iter)->pos.y() < pos.y() + size.y()) { + if((*iter)->component->getParent() == this) + (*iter)->component->setParent(NULL); + delete *iter; iter = mEntries.erase(iter); }else{ @@ -305,14 +308,22 @@ bool ComponentListComponent::input(InputConfig* config, Input input) void ComponentListComponent::resetCursor() { - if(mEntries.size() == 0) + auto iter = mEntries.begin(); + while(iter != mEntries.end()) + { + if((*iter)->canFocus) + break; + iter++; + } + + if(iter == mEntries.end()) { mCursor = Eigen::Vector2i(-1, -1); return; } const Eigen::Vector2i origCursor = mCursor; - mCursor << mEntries.at(0)->pos[0], mEntries.at(0)->pos[1]; + mCursor << (*iter)->pos[0], (*iter)->pos[1]; onCursorMoved(origCursor, mCursor); } diff --git a/src/components/GuiGameScraper.cpp b/src/components/GuiGameScraper.cpp index d63070dd6..d8111f7a4 100644 --- a/src/components/GuiGameScraper.cpp +++ b/src/components/GuiGameScraper.cpp @@ -13,6 +13,7 @@ GuiGameScraper::GuiGameScraper(Window* window, ScraperSearchParams params, std:: mResultName(window, "", Font::get(*window->getResourceManager(), Font::getDefaultPath(), FONT_SIZE_MEDIUM)), mResultInfo(window), mResultDesc(window, "", Font::get(*window->getResourceManager(), Font::getDefaultPath(), FONT_SIZE_SMALL)), + mResultThumbnail(window), mSearchLabel(window, "Search for: ", Font::get(*window->getResourceManager(), Font::getDefaultPath(), FONT_SIZE_SMALL)), mSearchText(window), @@ -57,13 +58,16 @@ GuiGameScraper::GuiGameScraper(Window* window, ScraperSearchParams params, std:: mResultName.setColor(0x3B56CCFF); mList.setEntry(Vector2i(0, 1), Vector2i(1, 1), &mResultName, false, ComponentListComponent::AlignLeft); - mResultDesc.setText(params.game->metadata()->get("desc")); mResultDesc.setSize(colWidth, 0); mResultInfo.addChild(&mResultDesc); mResultInfo.setSize(mResultDesc.getSize().x(), mResultDesc.getFont()->getHeight() * 3.0f); mList.setEntry(Vector2i(0, 2), Vector2i(1, 1), &mResultInfo, false, ComponentListComponent::AlignLeft); + mResultThumbnail.setOrigin(0.5f, 0.5f); + mResultThumbnail.setResize(colWidth, mResultInfo.getSize().y(), false); + mList.setEntry(Vector2i(1, 2), Vector2i(1, 1), &mResultThumbnail, false, ComponentListComponent::AlignCenter); + //y = 3 is a spacer row mList.setEntry(Vector2i(0, 4), Vector2i(1, 1), &mSearchLabel, false, ComponentListComponent::AlignLeft); @@ -87,6 +91,8 @@ GuiGameScraper::GuiGameScraper(Window* window, ScraperSearchParams params, std:: mBox.fitTo(mList.getSize(), mList.getPosition()); mResultInfo.setAutoScroll(2200, 0.015f); + + mList.resetCursor(); } void GuiGameScraper::search() @@ -135,7 +141,7 @@ bool GuiGameScraper::input(InputConfig* config, Input input) if(config->isMappedTo("a", input) && input.value != 0) { //if you're on a result - if(getSelectedIndex()) + if(getSelectedIndex() != -1) { mDoneFunc(mScraperResults.at(getSelectedIndex())); delete this; @@ -149,9 +155,10 @@ bool GuiGameScraper::input(InputConfig* config, Input input) return true; } + bool wasEditing = mSearchText.isEditing(); bool ret = GuiComponent::input(config, input); - if(config->isMappedTo("up", input) || config->isMappedTo("down", input)) + if(config->isMappedTo("up", input) || config->isMappedTo("down", input) && input.value != 0) { //update game info pane int i = getSelectedIndex(); @@ -161,8 +168,46 @@ bool GuiGameScraper::input(InputConfig* config, Input input) 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(); } } + //stopped editing + if(wasEditing && !mSearchText.isEditing()) + { + //update results + search(); + } + return ret; } + +void GuiGameScraper::update(int deltaTime) +{ + if(mThumbnailReq && mThumbnailReq->status() != HttpReq::REQ_IN_PROGRESS) + { + updateThumbnail(); + } + + GuiComponent::update(deltaTime); +} + +void GuiGameScraper::updateThumbnail() +{ + if(mThumbnailReq && mThumbnailReq->status() == HttpReq::REQ_SUCCESS) + { + std::string content = mThumbnailReq->getContent(); + mResultThumbnail.setImage(content.data(), content.length()); + }else{ + LOG(LogWarning) << "thumbnail req failed: " << mThumbnailReq->getErrorMsg(); + mResultThumbnail.setImage(""); + } + + mThumbnailReq.reset(); +} diff --git a/src/components/GuiGameScraper.h b/src/components/GuiGameScraper.h index 1f29a5a6f..c70052bed 100644 --- a/src/components/GuiGameScraper.h +++ b/src/components/GuiGameScraper.h @@ -8,6 +8,8 @@ #include "TextEditComponent.h" #include "NinePatchComponent.h" #include "../Settings.h" +#include "../HttpReq.h" +#include "ImageComponent.h" class GuiGameScraper : public GuiComponent { @@ -15,11 +17,13 @@ public: GuiGameScraper(Window* window, ScraperSearchParams params, std::function doneFunc, std::function skipFunc = NULL); bool input(InputConfig* config, Input input) override; + void update(int deltaTime) override; void search(); private: int getSelectedIndex(); void onSearchDone(std::vector results); + void updateThumbnail(); ComponentListComponent mList; NinePatchComponent mBox; @@ -29,6 +33,7 @@ private: TextComponent mResultName; ScrollableContainer mResultInfo; TextComponent mResultDesc; + ImageComponent mResultThumbnail; TextComponent mSearchLabel; TextEditComponent mSearchText; @@ -41,4 +46,6 @@ private: std::function mDoneFunc; std::function mSkipFunc; + + std::unique_ptr mThumbnailReq; }; diff --git a/src/components/ImageComponent.cpp b/src/components/ImageComponent.cpp index 6ed1f7cd1..e86015237 100644 --- a/src/components/ImageComponent.cpp +++ b/src/components/ImageComponent.cpp @@ -81,6 +81,16 @@ void ImageComponent::setImage(std::string path) resize(); } +void ImageComponent::setImage(const char* path, size_t length) +{ + mTexture.reset(); + + mTexture = TextureResource::get(*mWindow->getResourceManager(), ""); + mTexture->initFromMemory(path, length); + + resize(); +} + void ImageComponent::setOrigin(float originX, float originY) { mOrigin << originX, originY; diff --git a/src/components/ImageComponent.h b/src/components/ImageComponent.h index 92509492a..df0d1ece8 100644 --- a/src/components/ImageComponent.h +++ b/src/components/ImageComponent.h @@ -20,6 +20,7 @@ public: void copyScreen(); //Copy the entire screen into a texture for us to use. void setImage(std::string path); //Loads the image at the given filepath. + void setImage(const char* image, size_t length); //Loads image from memory. void setOrigin(float originX, float originY); //Sets the origin as a percentage of this image (e.g. (0, 0) is top left, (0.5, 0.5) is the center) void setTiling(bool tile); //Enables or disables tiling. Must be called before loading an image or resizing will be weird. void setResize(float width, float height, bool allowUpscale); diff --git a/src/components/TextEditComponent.cpp b/src/components/TextEditComponent.cpp index 7574d2a93..6beb5afee 100644 --- a/src/components/TextEditComponent.cpp +++ b/src/components/TextEditComponent.cpp @@ -137,6 +137,9 @@ void TextEditComponent::onTextChanged() std::string wrappedText = (isMultiline() ? f->wrapText(mText, mSize.x()) : mText); mTextCache = std::unique_ptr(f->buildTextCache(wrappedText, 0, 0, 0x00000000 | getOpacity())); + + if(mCursor > (int)mText.length()) + mCursor = mText.length(); } void TextEditComponent::onCursorChanged() @@ -215,3 +218,7 @@ bool TextEditComponent::isMultiline() return (getSize().y() > (float)getFont()->getHeight()); } +bool TextEditComponent::isEditing() const +{ + return mEditing; +} diff --git a/src/components/TextEditComponent.h b/src/components/TextEditComponent.h index 7eaef527d..d9117597a 100644 --- a/src/components/TextEditComponent.h +++ b/src/components/TextEditComponent.h @@ -23,6 +23,8 @@ public: void setValue(const std::string& val) override; std::string getValue() const override; + bool isEditing() const; + private: void onTextChanged(); void onCursorChanged(); diff --git a/src/resources/TextureResource.cpp b/src/resources/TextureResource.cpp index e46992cb2..e0540796a 100644 --- a/src/resources/TextureResource.cpp +++ b/src/resources/TextureResource.cpp @@ -79,6 +79,34 @@ void TextureResource::initFromScreen() mTextureSize[1] = height; } +void TextureResource::initFromMemory(const char* data, size_t length) +{ + deinit(); + + size_t width, height; + std::vector imageRGBA = ImageIO::loadFromMemoryRGBA32((const unsigned char*)(data), length, width, height); + + if(imageRGBA.size() == 0) + { + LOG(LogError) << "Could not initialize texture from memory (invalid data)!"; + return; + } + + //now for the openGL texture stuff + glGenTextures(1, &mTextureID); + glBindTexture(GL_TEXTURE_2D, mTextureID); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageRGBA.data()); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + + mTextureSize << width, height; +} + void TextureResource::deinit() { if(mTextureID != 0) diff --git a/src/resources/TextureResource.h b/src/resources/TextureResource.h index fb72eeb82..85230aff3 100644 --- a/src/resources/TextureResource.h +++ b/src/resources/TextureResource.h @@ -21,6 +21,7 @@ public: void bind() const; void initFromScreen(); + void initFromMemory(const char* image, size_t length); private: TextureResource(const ResourceManager& rm, const std::string& path); diff --git a/src/scrapers/GamesDBScraper.cpp b/src/scrapers/GamesDBScraper.cpp index 84143d7d8..08e5d03be 100644 --- a/src/scrapers/GamesDBScraper.cpp +++ b/src/scrapers/GamesDBScraper.cpp @@ -54,6 +54,18 @@ std::vector GamesDBScraper::parseReq(ScraperSearchParams params, s mdl.push_back(MetaDataList(params.system->getGameMDD())); mdl.back().set("name", game.child("GameTitle").text().get()); mdl.back().set("desc", game.child("Overview").text().get()); + pugi::xml_node images = game.child("Images"); + + if(images) + { + pugi::xml_node art = images.find_child_by_attribute("boxart", "side", "front"); + + if(art) + { + mdl.back().set("thumbnail", baseImageUrl + art.attribute("thumb").as_string()); + mdl.back().set("image", baseImageUrl + art.text().get()); + } + } resultNum++; game = game.next_sibling("Game"); @@ -75,4 +87,3 @@ void GamesDBScraper::getResultsAsync(ScraperSearchParams params, Window* window, window->pushGui(req); } -