From b894f0b2283a4fcd42fc41f9517b16d623ae3adb Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 5 Aug 2019 21:07:11 +0200 Subject: Add thumbnails for manganelo --- src/Body.cpp | 196 ++++++++++++++++++++++++++++++++++++++++++++++ src/Plugin.cpp | 52 ------------ src/QuickMedia.cpp | 156 ------------------------------------ src/plugins/Manganelo.cpp | 3 + src/plugins/Plugin.cpp | 52 ++++++++++++ 5 files changed, 251 insertions(+), 208 deletions(-) create mode 100644 src/Body.cpp delete mode 100644 src/Plugin.cpp create mode 100644 src/plugins/Plugin.cpp (limited to 'src') diff --git a/src/Body.cpp b/src/Body.cpp new file mode 100644 index 0000000..5b58df8 --- /dev/null +++ b/src/Body.cpp @@ -0,0 +1,196 @@ +#include "../include/Body.hpp" +#include "../plugins/Plugin.hpp" +#include +#include +#include + +const sf::Color front_color(43, 45, 47); +const sf::Color back_color(33, 35, 37); + +namespace QuickMedia { + Body::Body(sf::Font &font) : title_text("", font, 14), selected_item(0) { + title_text.setFillColor(sf::Color::White); + } + + void Body::select_previous_item() { + if(items.empty()) + return; + + int num_items = (int)items.size(); + for(int i = 0; i < num_items; ++i) { + --selected_item; + if(selected_item < 0) + selected_item = num_items - 1; + if(items[selected_item]->visible) + return; + } + } + + void Body::select_next_item() { + if(items.empty()) + return; + + int num_items = (int)items.size(); + for(int i = 0; i < num_items; ++i) { + ++selected_item; + if(selected_item == num_items) + selected_item = 0; + if(items[selected_item]->visible) + return; + } + } + + void Body::reset_selected() { + selected_item = 0; + } + + void Body::clear_items() { + items.clear(); + item_thumbnail_textures.clear(); + } + + BodyItem* Body::get_selected() const { + if(items.empty() || !items[selected_item]->visible) + return nullptr; + return items[selected_item].get(); + } + + void Body::clamp_selection() { + int num_items = (int)items.size(); + if(items.empty()) + return; + + if(selected_item < 0) + selected_item = 0; + else if(selected_item >= num_items) + selected_item = num_items - 1; + + for(int i = selected_item; i >= 0; --i) { + if(items[i]->visible) { + selected_item = i; + return; + } + } + + for(int i = selected_item; i < num_items; ++i) { + if(items[i]->visible) { + selected_item = i; + return; + } + } + } + + static std::unique_ptr load_texture_from_url(const std::string &url) { + auto result = std::make_unique(); + result->setSmooth(true); + std::string texture_data; + if(download_to_string(url, texture_data) == DownloadResult::OK) + result->loadFromMemory(texture_data.data(), texture_data.size()); + return result; + } + + // TODO: Skip drawing the rows that are outside the window. + // TODO: Use a render target for the whole body so all images can be put into one. + // TODO: Only load images once they are visible on the screen. + // TODO: Load images asynchronously to prevent scroll lag and to improve load time of suggestions. + void Body::draw(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size) { + const float font_height = title_text.getCharacterSize() + 8.0f; + const float image_height = 50.0f; + + sf::RectangleShape image_fallback(sf::Vector2f(50, image_height)); + image_fallback.setFillColor(sf::Color::White); + + sf::Sprite image; + + sf::RectangleShape item_background; + item_background.setFillColor(front_color); + item_background.setOutlineThickness(1.0f); + item_background.setOutlineColor(sf::Color(63, 65, 67)); + + sf::RectangleShape selected_border; + selected_border.setFillColor(sf::Color::Red); + const float selected_border_width = 5.0f; + + int num_items = items.size(); + if((int)item_thumbnail_textures.size() != num_items) + item_thumbnail_textures.resize(num_items); + + for(int i = 0; i < num_items; ++i) { + const auto &item = items[i]; + assert(items.size() == item_thumbnail_textures.size()); + auto &item_thumbnail = item_thumbnail_textures[i]; + if(!item->visible) + continue; + + bool draw_thumbnail = !item->thumbnail_url.empty(); + float row_height = font_height; + if(draw_thumbnail) { + row_height = image_height; + if(!item_thumbnail) + item_thumbnail = load_texture_from_url(item->thumbnail_url); + } + + sf::Vector2f item_pos = pos; + if(i == selected_item) { + selected_border.setPosition(pos); + selected_border.setSize(sf::Vector2f(selected_border_width, row_height)); + window.draw(selected_border); + item_pos.x += selected_border_width; + item_background.setFillColor(front_color); + } else { + item_background.setFillColor(sf::Color(38, 40, 42)); + } + + item_background.setPosition(item_pos); + item_background.setSize(sf::Vector2f(size.x, row_height)); + window.draw(item_background); + + float text_offset_x = 0.0f; + if(draw_thumbnail) { + if(item_thumbnail && item_thumbnail->getNativeHandle() != 0) { + image.setTexture(*item_thumbnail, true); + auto image_size = image.getTexture()->getSize(); + auto height_ratio = image_height / image_size.y; + auto scale = image.getScale(); + auto image_scale_ratio = scale.x / scale.y; + const float width_ratio = height_ratio * image_scale_ratio; + image.setScale(width_ratio, height_ratio); + image.setPosition(item_pos); + window.draw(image); + text_offset_x = width_ratio * image_size.x; + } else { + image_fallback.setPosition(item_pos); + window.draw(image_fallback); + } + } + + title_text.setString(item->title); + title_text.setPosition(item_pos.x + text_offset_x + 10.0f, item_pos.y); + window.draw(title_text); + + pos.y += row_height + 10.0f; + } + } + + //static + bool Body::string_find_case_insensitive(const std::string &str, const std::string &substr) { + auto it = std::search(str.begin(), str.end(), substr.begin(), substr.end(), + [](char c1, char c2) { + return std::toupper(c1) == std::toupper(c2); + }); + return it != str.end(); + } + + void Body::filter_search_fuzzy(const std::string &text) { + if(text.empty()) { + for(auto &item : items) { + item->visible = true; + } + return; + } + + for(auto &item : items) { + item->visible = string_find_case_insensitive(item->title, text); + } + } +} \ No newline at end of file diff --git a/src/Plugin.cpp b/src/Plugin.cpp deleted file mode 100644 index c31e715..0000000 --- a/src/Plugin.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "../plugins/Plugin.hpp" -#include "../include/Program.h" -#include -#include - -static int accumulate_string(char *data, int size, void *userdata) { - std::string *str = (std::string*)userdata; - str->append(data, size); - return 0; -} - -namespace QuickMedia { - SuggestionResult Plugin::update_search_suggestions(const std::string &text, std::vector> &result_items) { - (void)text; - (void)result_items; - return SuggestionResult::OK; - } - - std::vector> Plugin::get_related_media(const std::string &url) { - (void)url; - return {}; - } - - DownloadResult Plugin::download_to_string(const std::string &url, std::string &result, const std::vector &additional_args) { - std::vector args = { "curl", "-H", "Accept-Language: en-US,en;q=0.5", "--compressed", "-s", "-L", url.c_str() }; - for(const CommandArg &arg : additional_args) { - args.push_back(arg.option.c_str()); - args.push_back(arg.value.c_str()); - } - args.push_back(nullptr); - if(exec_program(args.data(), accumulate_string, &result) != 0) - return DownloadResult::NET_ERR; - return DownloadResult::OK; - } - - std::string Plugin::url_param_encode(const std::string ¶m) const { - std::ostringstream result; - result.fill('0'); - result << std::hex; - - for(char c : param) { - if(isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { - result << c; - } else { - result << std::uppercase; - result << "%" << std::setw(2) << (int)(unsigned char)(c); - } - } - - return result.str(); - } -} \ No newline at end of file diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 5f65fdd..a867255 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -12,162 +12,6 @@ const sf::Color front_color(43, 45, 47); const sf::Color back_color(33, 35, 37); namespace QuickMedia { - class Body { - public: - Body(sf::Font &font) : title_text("", font, 14), selected_item(0) { - title_text.setFillColor(sf::Color::White); - } - - void add_item(std::unique_ptr item) { - items.push_back(std::move(item)); - } - - // Select previous item, ignoring invisible items - void select_previous_item() { - if(items.empty()) - return; - - int num_items = (int)items.size(); - for(int i = 0; i < num_items; ++i) { - --selected_item; - if(selected_item < 0) - selected_item = num_items - 1; - if(items[selected_item]->visible) - return; - } - } - - // Select next item, ignoring invisible items - void select_next_item() { - if(items.empty()) - return; - - int num_items = (int)items.size(); - for(int i = 0; i < num_items; ++i) { - ++selected_item; - if(selected_item == num_items) - selected_item = 0; - if(items[selected_item]->visible) - return; - } - } - - void reset_selected() { - selected_item = 0; - } - - void clear_items() { - items.clear(); - } - - BodyItem* get_selected() const { - if(items.empty() || !items[selected_item]->visible) - return nullptr; - return items[selected_item].get(); - } - - void clamp_selection() { - int num_items = (int)items.size(); - if(items.empty()) - return; - - if(selected_item < 0) - selected_item = 0; - else if(selected_item >= num_items) - selected_item = num_items - 1; - - for(int i = selected_item; i >= 0; --i) { - if(items[i]->visible) { - selected_item = i; - return; - } - } - - for(int i = selected_item; i < num_items; ++i) { - if(items[i]->visible) { - selected_item = i; - return; - } - } - } - - void draw(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size) { - const float font_height = title_text.getCharacterSize() + 8.0f; - const float image_height = 50.0f; - - sf::RectangleShape image(sf::Vector2f(50, image_height)); - image.setFillColor(sf::Color::White); - - sf::RectangleShape item_background; - item_background.setFillColor(front_color); - item_background.setOutlineThickness(1.0f); - item_background.setOutlineColor(sf::Color(63, 65, 67)); - - sf::RectangleShape selected_border(sf::Vector2f(5.0f, 50)); - selected_border.setFillColor(sf::Color::Red); - - int i = 0; - for(const auto &item : items) { - if(!item->visible) { - ++i; - continue; - } - - sf::Vector2f item_pos = pos; - if(i == selected_item) { - selected_border.setPosition(pos); - window.draw(selected_border); - item_pos.x += selected_border.getSize().x; - item_background.setFillColor(front_color); - } else { - item_background.setFillColor(sf::Color(38, 40, 42)); - } - - item_background.setPosition(item_pos); - item_background.setSize(sf::Vector2f(size.x, 50)); - window.draw(item_background); - - image.setPosition(item_pos); - window.draw(image); - - title_text.setString(item->title); - title_text.setPosition(item_pos.x + 50 + 10, item_pos.y); - window.draw(title_text); - - - pos.y += 50 + 10; - ++i; - } - } - - static bool string_find_case_insensitive(const std::string &str, const std::string &substr) { - auto it = std::search(str.begin(), str.end(), substr.begin(), substr.end(), - [](char c1, char c2) { - return std::toupper(c1) == std::toupper(c2); - }); - return it != str.end(); - } - - // TODO: Make this actually fuzzy... Right now it's just a case insensitive string find. - // TODO: Highlight the part of the text that matches the search - void filter_search_fuzzy(const std::string &text) { - if(text.empty()) { - for(auto &item : items) { - item->visible = true; - } - return; - } - - for(auto &item : items) { - item->visible = string_find_case_insensitive(item->title, text); - } - } - - sf::Text title_text; - int selected_item; - std::vector> items; - }; - Program::Program() : window(sf::VideoMode(800, 600), "QuickMedia"), window_size(800, 600), diff --git a/src/plugins/Manganelo.cpp b/src/plugins/Manganelo.cpp index 6dd4ec7..e8d24dc 100644 --- a/src/plugins/Manganelo.cpp +++ b/src/plugins/Manganelo.cpp @@ -85,6 +85,9 @@ namespace QuickMedia { if(name_str != text) { auto item = std::make_unique(name_str); item->url = "https://manganelo.com/manga/" + url_param_encode(nameunsigned.asString()); + Json::Value image = child.get("image", ""); + if(image.isString() && image.asCString()[0] != '\0') + item->thumbnail_url = image.asString(); result_items.push_back(std::move(item)); } } diff --git a/src/plugins/Plugin.cpp b/src/plugins/Plugin.cpp new file mode 100644 index 0000000..d87ac34 --- /dev/null +++ b/src/plugins/Plugin.cpp @@ -0,0 +1,52 @@ +#include "../../plugins/Plugin.hpp" +#include "../../include/Program.h" +#include +#include + +static int accumulate_string(char *data, int size, void *userdata) { + std::string *str = (std::string*)userdata; + str->append(data, size); + return 0; +} + +namespace QuickMedia { + SuggestionResult Plugin::update_search_suggestions(const std::string &text, std::vector> &result_items) { + (void)text; + (void)result_items; + return SuggestionResult::OK; + } + + std::vector> Plugin::get_related_media(const std::string &url) { + (void)url; + return {}; + } + + DownloadResult download_to_string(const std::string &url, std::string &result, const std::vector &additional_args) { + std::vector args = { "curl", "-H", "Accept-Language: en-US,en;q=0.5", "--compressed", "-s", "-L", url.c_str() }; + for(const CommandArg &arg : additional_args) { + args.push_back(arg.option.c_str()); + args.push_back(arg.value.c_str()); + } + args.push_back(nullptr); + if(exec_program(args.data(), accumulate_string, &result) != 0) + return DownloadResult::NET_ERR; + return DownloadResult::OK; + } + + std::string Plugin::url_param_encode(const std::string ¶m) const { + std::ostringstream result; + result.fill('0'); + result << std::hex; + + for(char c : param) { + if(isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { + result << c; + } else { + result << std::uppercase; + result << "%" << std::setw(2) << (int)(unsigned char)(c); + } + } + + return result.str(); + } +} \ No newline at end of file -- cgit v1.2.3