aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2019-08-05 21:07:11 +0200
committerdec05eba <dec05eba@protonmail.com>2019-08-05 21:07:14 +0200
commitb894f0b2283a4fcd42fc41f9517b16d623ae3adb (patch)
treebca95c9bcf3947886fd85d1759cb8349a563bfd2
parent2b294258bbc89f1b49554468022a035782e49074 (diff)
Add thumbnails for manganelo
-rw-r--r--README.md3
-rw-r--r--include/Body.hpp48
-rw-r--r--plugins/Plugin.hpp16
-rw-r--r--src/Body.cpp196
-rw-r--r--src/QuickMedia.cpp156
-rw-r--r--src/plugins/Manganelo.cpp3
-rw-r--r--src/plugins/Plugin.cpp (renamed from src/Plugin.cpp)6
7 files changed, 255 insertions, 173 deletions
diff --git a/README.md b/README.md
index 7de0d75..9937445 100644
--- a/README.md
+++ b/README.md
@@ -13,4 +13,5 @@ Fix x11 freeze that sometimes happens when playing video.
If a search returns no results, then "No results found for ..." should be shown and navigation should go back to searching with suggestions.
Keep track of content that has been viewed so the user can return to where they were last.
For manga, view the next chapter when reaching the end of a chapter.
-Make network requests asynchronous to not freeze gui when navigating. Also have loading animation. \ No newline at end of file
+Make network requests asynchronous to not freeze gui when navigating. Also have loading animation.
+Retain search text when navigating back. \ No newline at end of file
diff --git a/include/Body.hpp b/include/Body.hpp
new file mode 100644
index 0000000..c27ecf0
--- /dev/null
+++ b/include/Body.hpp
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <SFML/Graphics/Font.hpp>
+#include <SFML/Graphics/Text.hpp>
+#include <SFML/Graphics/Texture.hpp>
+#include <SFML/Graphics/RenderWindow.hpp>
+
+namespace QuickMedia {
+ class BodyItem {
+ public:
+ BodyItem(const std::string &_title): title(_title), visible(true) {
+
+ }
+
+ std::string title;
+ std::string url;
+ std::string thumbnail_url;
+ bool visible;
+ };
+
+ class Body {
+ public:
+ Body(sf::Font &font);
+
+ // Select previous item, ignoring invisible items
+ void select_previous_item();
+
+ // Select next item, ignoring invisible items
+ void select_next_item();
+ void reset_selected();
+ void clear_items();
+
+ BodyItem* get_selected() const;
+
+ void clamp_selection();
+ void draw(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size);
+ static bool string_find_case_insensitive(const std::string &str, const std::string &substr);
+
+ // 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);
+
+ sf::Text title_text;
+ int selected_item;
+ std::vector<std::unique_ptr<BodyItem>> items;
+ std::vector<std::unique_ptr<sf::Texture>> item_thumbnail_textures;
+ };
+} \ No newline at end of file
diff --git a/plugins/Plugin.hpp b/plugins/Plugin.hpp
index 818cc5f..2d6005e 100644
--- a/plugins/Plugin.hpp
+++ b/plugins/Plugin.hpp
@@ -1,23 +1,12 @@
#pragma once
#include "../include/Page.hpp"
+#include "../include/Body.hpp"
#include <string>
#include <vector>
#include <memory>
namespace QuickMedia {
- class BodyItem {
- public:
- BodyItem(const std::string &_title): title(_title), visible(true) {
-
- }
-
- std::string title;
- std::string url;
- std::string thumbnail_url;
- bool visible;
- };
-
enum class SearchResult {
OK,
ERR,
@@ -48,6 +37,8 @@ namespace QuickMedia {
std::string value;
};
+ DownloadResult download_to_string(const std::string &url, std::string &result, const std::vector<CommandArg> &additional_args = {});
+
class Plugin {
public:
virtual ~Plugin() = default;
@@ -56,7 +47,6 @@ namespace QuickMedia {
virtual SuggestionResult update_search_suggestions(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items);
virtual std::vector<std::unique_ptr<BodyItem>> get_related_media(const std::string &url);
protected:
- DownloadResult download_to_string(const std::string &url, std::string &result, const std::vector<CommandArg> &additional_args = {});
std::string url_param_encode(const std::string &param) const;
};
} \ No newline at end of file
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 <SFML/Graphics/RectangleShape.hpp>
+#include <SFML/Graphics/Sprite.hpp>
+#include <assert.h>
+
+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<sf::Texture> load_texture_from_url(const std::string &url) {
+ auto result = std::make_unique<sf::Texture>();
+ 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/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<BodyItem> 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<std::unique_ptr<BodyItem>> 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<BodyItem>(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/Plugin.cpp b/src/plugins/Plugin.cpp
index c31e715..d87ac34 100644
--- a/src/Plugin.cpp
+++ b/src/plugins/Plugin.cpp
@@ -1,5 +1,5 @@
-#include "../plugins/Plugin.hpp"
-#include "../include/Program.h"
+#include "../../plugins/Plugin.hpp"
+#include "../../include/Program.h"
#include <sstream>
#include <iomanip>
@@ -21,7 +21,7 @@ namespace QuickMedia {
return {};
}
- DownloadResult Plugin::download_to_string(const std::string &url, std::string &result, const std::vector<CommandArg> &additional_args) {
+ DownloadResult download_to_string(const std::string &url, std::string &result, const std::vector<CommandArg> &additional_args) {
std::vector<const char*> 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());