From 07f80da8f9c46c228c272e95ab88a3e098c899d9 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Tue, 30 Jun 2020 02:25:10 +0200 Subject: Add infinite image scroll mode --- src/ImageViewer.cpp | 249 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/QuickMedia.cpp | 68 +++++++++++++- 2 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 src/ImageViewer.cpp (limited to 'src') 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 +#include +#include + +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 image_size = get_page_size(page); + std::unique_ptr &page_image_data = image_data[page]; + sf::Vector2 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 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 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(); + 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 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 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 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 ImageViewer::get_page_size(int page) { + const sf::Vector2 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(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 #include @@ -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(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 ¤t = 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); -- cgit v1.2.3