aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ImageViewer.cpp249
-rw-r--r--src/QuickMedia.cpp68
2 files changed, 316 insertions, 1 deletions
diff --git a/src/ImageViewer.cpp b/src/ImageViewer.cpp
new file mode 100644
index 0000000..fcf9519
--- /dev/null
+++ b/src/ImageViewer.cpp
@@ -0,0 +1,249 @@
+#include "../include/ImageViewer.hpp"
+#include "../include/Notification.hpp"
+#include "../include/Storage.hpp"
+#include "../plugins/Manga.hpp"
+#include <cmath>
+#include <SFML/Window/Event.hpp>
+#include <SFML/Graphics/RectangleShape.hpp>
+
+namespace QuickMedia {
+ ImageViewer::ImageViewer(Manga *manga, const std::string &images_url, const std::string &chapter_title, int current_page, const Path &chapter_cache_dir, sf::Font *font) :
+ current_page(current_page),
+ num_pages(0),
+ chapter_title(chapter_title),
+ chapter_cache_dir(chapter_cache_dir),
+ focused_page(current_page),
+ font(font),
+ page_text("", *font, 14)
+ {
+ if(manga->get_number_of_images(images_url, num_pages) != ImageResult::OK) {
+ show_notification("Plugin", "Failed to get number of images", Urgency::CRITICAL);
+ return;
+ }
+ current_page = std::min(current_page, num_pages);
+ image_data.resize(num_pages);
+ page_size.resize(num_pages);
+ for(int i = 0; i < num_pages; ++i) {
+ image_data[i] = nullptr;
+ page_size[i].loaded = false;
+ }
+ page_text.setFillColor(sf::Color::White);
+ }
+
+ bool ImageViewer::render_page(sf::RenderWindow &window, int page, double offset_y) {
+ if(page < 0 || page >= (int)image_data.size())
+ return false;
+
+ const sf::Vector2<double> image_size = get_page_size(page);
+ std::unique_ptr<ImageData> &page_image_data = image_data[page];
+ sf::Vector2<double> render_pos(std::floor(window_size.x * 0.5 - image_size.x * 0.5), std::floor(- image_size.y * 0.5 + scroll + offset_y));
+ if(render_pos.y + image_size.y <= 0.0 || render_pos.y >= window_size.y) {
+ if(page_image_data)
+ page_image_data->visible_on_screen = false;
+ return true;
+ }
+
+ double center_dist = std::abs(window_size.y * 0.5 - (render_pos.y + image_size.y * 0.5));
+ if(center_dist < min_page_center_dist) {
+ min_page_center_dist = center_dist;
+ page_closest_to_center = page;
+ }
+
+ if(page_image_data) {
+ if(page_image_data->failed_to_load_image) {
+ sf::Text error_message("Failed to load image for page " + std::to_string(1 + page), *font, 30);
+ auto text_bounds = error_message.getLocalBounds();
+ error_message.setFillColor(sf::Color::Black);
+ sf::Vector2<double> render_pos_text(std::floor(window_size.x * 0.5 - text_bounds.width * 0.5), std::floor(- text_bounds.height * 0.5 + scroll + offset_y));
+
+ sf::RectangleShape background(sf::Vector2f(image_size.x, image_size.y));
+ background.setFillColor(sf::Color::White);
+ background.setPosition(render_pos.x, render_pos.y);
+ window.draw(background);
+
+ error_message.setPosition(render_pos_text.x, render_pos_text.y);
+ window.draw(error_message);
+ } else {
+ page_image_data->sprite.setPosition(render_pos.x, render_pos.y);
+ window.draw(page_image_data->sprite);
+ }
+ } else {
+ std::string page_str = std::to_string(1 + page);
+
+ sf::Text error_message("Downloading page " + page_str, *font, 30);
+ auto text_bounds = error_message.getLocalBounds();
+ error_message.setFillColor(sf::Color::Black);
+ sf::Vector2<double> render_pos_text(std::floor(window_size.x * 0.5 - text_bounds.width * 0.5), std::floor(- text_bounds.height * 0.5 + scroll + offset_y));
+
+ sf::RectangleShape background(sf::Vector2f(image_size.x, image_size.y));
+ background.setFillColor(sf::Color::White);
+ background.setPosition(render_pos.x, render_pos.y);
+ window.draw(background);
+
+ error_message.setPosition(render_pos_text.x, render_pos_text.y);
+ window.draw(error_message);
+
+ Path image_path = chapter_cache_dir;
+ image_path.join(page_str);
+
+ // TODO: Make image loading asynchronous
+ Path image_finished_path(image_path.data + ".finished");
+ if(get_file_type(image_finished_path) != FileType::FILE_NOT_FOUND && get_file_type(image_path) == FileType::REGULAR) {
+ fprintf(stderr, "ImageViewer: Load page %d\n", 1 + page);
+ page_image_data = std::make_unique<ImageData>();
+ page_image_data->visible_on_screen = true;
+ std::string image_data;
+ if(file_get_content(image_path, image_data) == 0) {
+ if(page_image_data->texture.loadFromMemory(image_data.data(), image_data.size())) {
+ page_image_data->texture.setSmooth(true);
+ page_image_data->sprite.setTexture(page_image_data->texture, true);
+ //image_texture.generateMipmap();
+ page_image_data->failed_to_load_image = false;
+ page_size[page].size = get_page_size(page);
+ page_size[page].loaded = true;
+ } else {
+ page_image_data->failed_to_load_image = true;
+ }
+ } else {
+ show_notification("Manga", "Failed to load image for page " + page_str + ". Image filepath: " + image_path.data, Urgency::CRITICAL);
+ page_image_data->failed_to_load_image = true;
+ }
+ }
+
+ }
+
+ return true;
+ }
+
+ bool ImageViewer::draw(sf::RenderWindow &window) {
+ const double frame_delta = frame_timer.restart().asSeconds();
+ const double scroll_speed_key_input = 450.0;
+ const double scroll_speed_mouse_wheel = 450.0;
+ const double scroll_deaccel = 0.93;
+
+ if(!window_size_set) {
+ auto window_size_i = window.getSize();
+ window_size.x = window_size_i.x;
+ window_size.y = window_size_i.y;
+ window_size_set = true;
+ }
+
+ // TODO: Only redraw when scrolling and when image has finished downloading
+ sf::Event event;
+ while(window.pollEvent(event)) {
+ if (event.type == sf::Event::Closed) {
+ //current_page = Page::EXIT;
+ window.close();
+ return false;
+ } else if(event.type == sf::Event::Resized) {
+ window_size.x = event.size.width;
+ window_size.y = event.size.height;
+ sf::FloatRect visible_area(0, 0, window_size.x, window_size.y);
+ window.setView(sf::View(visible_area));
+ //redraw = true;
+ } else if(event.type == sf::Event::GainedFocus) {
+ //redraw = true;
+ } else if(event.type == sf::Event::KeyPressed) {
+ if(event.key.code == sf::Keyboard::Up) {
+ scroll_speed += scroll_speed_key_input * frame_delta;
+ } else if(event.key.code == sf::Keyboard::Down) {
+ scroll_speed -= scroll_speed_key_input * frame_delta;
+ } else if(event.key.code == sf::Keyboard::Escape) {
+ return false;
+ }
+ } else if(event.type == sf::Event::MouseWheelScrolled && event.mouseWheelScroll.wheel == sf::Mouse::VerticalWheel) {
+ scroll_speed += scroll_speed_mouse_wheel * event.mouseWheelScroll.delta * frame_delta;
+ }
+ }
+
+ scroll += scroll_speed;
+ scroll *= scroll_deaccel;
+ if(std::abs(scroll) < 0.1)
+ scroll = 0.0;
+
+ min_page_center_dist = 9999999.0;
+ page_closest_to_center = -1;
+
+ const sf::Vector2<double> selected_page_size = get_page_size(current_page);
+ render_page(window, current_page, window_size.y*0.5);
+ //if(!focused_page_rendered)
+ // return;
+
+ // Render previous pages
+ double page_offset = window_size.y*0.5 - selected_page_size.y*0.5;
+ int page = current_page - 1;
+ while(true) {
+ const sf::Vector2<double> image_size = get_page_size(page);
+ page_offset -= image_size.y*0.5;
+ if(!render_page(window, page, page_offset))
+ break;
+ --page;
+ page_offset -= image_size.y*0.5;
+ }
+
+ // Render next pages
+ page_offset = window_size.y*0.5 + selected_page_size.y*0.5;
+ page = current_page + 1;
+ while(true) {
+ const sf::Vector2<double> image_size = get_page_size(page);
+ page_offset += image_size.y*0.5;
+ if(!render_page(window, page, page_offset))
+ break;
+ ++page;
+ page_offset += image_size.y*0.5;
+ }
+
+ if(page_closest_to_center != -1) {
+ focused_page = page_closest_to_center;
+ }
+
+ if(focused_page != prev_focused_page) {
+ prev_focused_page = focused_page;
+ page_text.setString(chapter_title + " | Page " + std::to_string(1 + focused_page) + "/" + std::to_string(num_pages));
+ }
+
+ const float font_height = page_text.getCharacterSize() + 8.0f;
+ const float background_height = font_height + 6.0f;
+
+ sf::RectangleShape page_text_background(sf::Vector2f(window_size.x, background_height));
+ page_text_background.setFillColor(sf::Color(0, 0, 0, 150));
+ page_text_background.setPosition(0.0f, window_size.y - background_height);
+ window.draw(page_text_background);
+
+ auto page_text_bounds = page_text.getLocalBounds();
+ page_text.setPosition(std::floor(window_size.x * 0.5f - page_text_bounds.width * 0.5f), std::floor(window_size.y - background_height * 0.5f - font_height * 0.5f));
+ window.draw(page_text);
+
+ // Free pages that are not visible on the screen
+ int i = 0;
+ for(auto &page_data : image_data) {
+ if(page_data && !page_data->visible_on_screen) {
+ fprintf(stderr, "ImageViewer: Unload page %d\n", 1 + i);
+ page_data.reset();
+ }
+ ++i;
+ }
+
+ return true;
+ }
+
+ int ImageViewer::get_focused_page() const {
+ return 1 + focused_page;
+ }
+
+ sf::Vector2<double> ImageViewer::get_page_size(int page) {
+ const sf::Vector2<double> no_image_page_size(720.0, 1280.0);
+
+ if(page < 0 || page >= (int)image_data.size())
+ return no_image_page_size;
+
+ if(page_size[page].loaded)
+ return page_size[page].size;
+
+ if(!image_data[page])
+ return no_image_page_size;
+
+ sf::Vector2u texture_size = image_data[page]->texture.getSize();
+ return sf::Vector2<double>(texture_size.x, texture_size.y);
+ }
+} \ No newline at end of file
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 881f2d6..9b57b0b 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -12,6 +12,7 @@
#include "../include/StringUtils.hpp"
#include "../include/GoogleCaptcha.hpp"
#include "../include/Notification.hpp"
+#include "../include/ImageViewer.hpp"
#include <cppcodec/base64_rfc4648.hpp>
#include <SFML/Graphics/RectangleShape.hpp>
@@ -234,6 +235,11 @@ namespace QuickMedia {
window.setKeyRepeatEnabled(true);
break;
}
+ case Page::IMAGES_CONTINUOUS: {
+ body->draw_thumbnails = false;
+ image_continuous_page();
+ break;
+ }
case Page::CONTENT_LIST: {
body->draw_thumbnails = true;
content_list_page();
@@ -796,7 +802,7 @@ namespace QuickMedia {
images_url = item->url;
chapter_title = item->title;
image_index = 0;
- current_page = Page::IMAGES;
+ current_page = Page::IMAGES_CONTINUOUS;
if(start_from_beginning)
return;
@@ -1181,6 +1187,66 @@ namespace QuickMedia {
}
}
+ void Program::image_continuous_page() {
+ search_bar->onTextUpdateCallback = nullptr;
+ search_bar->onTextSubmitCallback = nullptr;
+
+ assert(current_plugin->is_manga());
+ Manga *image_plugin = static_cast<Manga*>(current_plugin);
+
+ content_cache_dir = get_cache_dir().join(image_plugin->name).join(manga_id_base64).join(base64_encode(chapter_title));
+ if(create_directory_recursive(content_cache_dir) != 0) {
+ show_notification("Storage", "Failed to create directory: " + content_cache_dir.data, Urgency::CRITICAL);
+ current_page = Page::EPISODE_LIST;
+ return;
+ }
+ download_chapter_images_if_needed(image_plugin);
+
+ Json::Value &json_chapters = content_storage_json["chapters"];
+ Json::Value json_chapter;
+ int latest_read = 1 + image_index;
+ if(json_chapters.isObject()) {
+ json_chapter = json_chapters[chapter_title];
+ if(json_chapter.isObject()) {
+ const Json::Value &current = json_chapter["current"];
+ if(current.isNumeric())
+ latest_read = std::max(latest_read, current.asInt());
+ } else {
+ json_chapter = Json::Value(Json::objectValue);
+ }
+ } else {
+ json_chapters = Json::Value(Json::objectValue);
+ json_chapter = Json::Value(Json::objectValue);
+ }
+
+ ImageViewer image_viewer(image_plugin, images_url, chapter_title, image_index, content_cache_dir, &font);
+
+ json_chapter["current"] = std::min(latest_read, image_viewer.get_num_pages());
+ json_chapter["total"] = image_viewer.get_num_pages();
+ json_chapters[chapter_title] = json_chapter;
+ if(!save_manga_progress_json(content_storage_file, content_storage_json)) {
+ show_notification("Manga progress", "Failed to save manga progress", Urgency::CRITICAL);
+ }
+
+ while(current_page == Page::IMAGES_CONTINUOUS) {
+ window.clear(back_color);
+ if(!image_viewer.draw(window))
+ current_page = Page::EPISODE_LIST;
+ window.display();
+
+ int focused_page = image_viewer.get_focused_page();
+ if(focused_page > latest_read) {
+ latest_read = focused_page;
+ image_index = latest_read - 1;
+ json_chapter["current"] = latest_read;
+ json_chapters[chapter_title] = json_chapter;
+ if(!save_manga_progress_json(content_storage_file, content_storage_json)) {
+ show_notification("Manga progress", "Failed to save manga progress", Urgency::CRITICAL);
+ }
+ }
+ }
+ }
+
void Program::content_list_page() {
if(current_plugin->get_content_list(content_list_url, body->items) != PluginResult::OK) {
show_notification("Content list", "Failed to get content list for url: " + content_list_url, Urgency::CRITICAL);