#include "../include/ImageViewer.hpp" #include "../include/Notification.hpp" #include "../include/Storage.hpp" #include "../plugins/Manga.hpp" #include #include #include #include namespace QuickMedia { ImageViewer::ImageViewer(MangaImagesPage *manga_images_page, const std::string &content_title, const std::string &chapter_title, int current_page, const Path &chapter_cache_dir, sf::Font *font) : current_page(current_page), num_pages(0), content_title(content_title), chapter_title(chapter_title), chapter_cache_dir(chapter_cache_dir), focused_page(current_page), font(font), page_text("", *font, 14) { if(manga_images_page->get_number_of_images(num_pages) != ImageResult::OK) { show_notification("QuickMedia", "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); has_default_cursor = default_cursor.loadFromSystem(sf::Cursor::Arrow); has_size_vertical_cursor = size_vertical_cursor.loadFromSystem(sf::Cursor::SizeVertical); } void ImageViewer::load_image_async(const Path &path, std::shared_ptr image_data) { image_data->image_status = ImageStatus::LOADING; image_data->texture.setSmooth(true); assert(!loading_image); loading_image = true; image_loader_thread = std::thread([this, image_data, path]() mutable { auto image = std::make_unique(); if(image->loadFromFile(path.data)) { image_data->image = std::move(image); image_data->image_status = ImageStatus::LOADED; } else { image_data->image_status = ImageStatus::FAILED_TO_LOAD; } loading_image = false; }); image_loader_thread.detach(); } bool ImageViewer::render_page(sf::RenderWindow &window, int page, double offset_y) { if(page < 0 || page >= (int)image_data.size()) return false; std::shared_ptr &page_image_data = image_data[page]; const sf::Vector2 image_size = get_page_size(page); sf::Vector2 render_pos(std::floor(window_size.x * 0.5 - image_size.x * 0.5), - 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; } bool scrolling = (std::abs(scroll_speed) > 0.01f); if(!scrolling) render_pos.y = std::floor(render_pos.y); 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) { page_image_data->visible_on_screen = true; if(page_image_data->image_status == ImageStatus::APPLIED_TO_TEXTURE) { 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); std::string msg; if(page_image_data->image_status == ImageStatus::WAITING) { if(!loading_image) { Path image_path = chapter_cache_dir; image_path.join(page_str); load_image_async(image_path, page_image_data); } msg = "Loading image for page " + page_str; } else if(page_image_data->image_status == ImageStatus::LOADING) { msg = "Loading image for page " + page_str; } else if(page_image_data->image_status == ImageStatus::FAILED_TO_LOAD) { msg = "Failed to load image for page " + page_str; } sf::Text error_message(std::move(msg), *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), - text_bounds.height * 0.5 + scroll + offset_y); if(!scrolling) render_pos_text.y = std::floor(render_pos_text.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 { 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), - text_bounds.height * 0.5 + scroll + offset_y); if(!scrolling) render_pos_text.y = std::floor(render_pos_text.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); if(get_file_type(image_path) == FileType::REGULAR) { fprintf(stderr, "ImageViewer: Loaded page %d\n", 1 + page); page_image_data = std::make_shared(); page_image_data->visible_on_screen = true; page_image_data->image_status = ImageStatus::WAITING; page_image_data->texture.setSmooth(true); } } return true; } static double sign(double value) { return value >= 0.0 ? 1.0 : -1.0; } ImageViewerAction ImageViewer::draw(sf::RenderWindow &window) { const double frame_delta = frame_timer.restart().asSeconds(); const double scroll_speed_key_input = 50.0; const double scroll_speed_mouse_wheel = 600.0; const double scroll_speed_autoscroll = 10.0; const double scroll_deaccel = 0.96; 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) { if(has_default_cursor) window.setMouseCursor(default_cursor); window.close(); return ImageViewerAction::RETURN; } 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) up_pressed = true; if(event.key.code == sf::Keyboard::Down) down_pressed = true; if(event.key.code == sf::Keyboard::Escape) { if(has_default_cursor) window.setMouseCursor(default_cursor); return ImageViewerAction::RETURN; } if(event.key.code == sf::Keyboard::I) return ImageViewerAction::SWITCH_TO_SINGLE_IMAGE_MODE; } else if(event.type == sf::Event::KeyReleased) { if(event.key.code == sf::Keyboard::Up) up_pressed = false; if(event.key.code == sf::Keyboard::Down) down_pressed = 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; } else if(event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Button::Middle) { middle_mouse_scrolling = true; autoscroll_start_y = sf::Mouse::getPosition(window).y; if(has_size_vertical_cursor) window.setMouseCursor(size_vertical_cursor); } else if(event.type == sf::Event::MouseButtonReleased && event.mouseButton.button == sf::Mouse::Button::Middle) { middle_mouse_scrolling = false; scroll_speed = 0.0; if(has_default_cursor) window.setMouseCursor(default_cursor); } } if(up_pressed) scroll_speed += scroll_speed_key_input * frame_delta; if(down_pressed) scroll_speed -= scroll_speed_key_input * frame_delta; if(middle_mouse_scrolling) { double distance_to_start_y = (double)sf::Mouse::getPosition(window).y - autoscroll_start_y; double dist_abs = std::abs(distance_to_start_y); dist_abs -= 20.0; if(dist_abs < 0.0) dist_abs = 0.0; scroll_speed = -(dist_abs * sign(distance_to_start_y)) * scroll_speed_autoscroll * frame_delta; const double max_speed = 100.0; if(scroll_speed > max_speed) scroll_speed = max_speed; if(scroll_speed < -max_speed) scroll_speed = -max_speed; scroll += scroll_speed; } else { const double max_speed = 100.0; if(scroll_speed > max_speed) scroll_speed = max_speed; if(scroll_speed < -max_speed) scroll_speed = -max_speed; scroll_speed *= scroll_deaccel; if(std::abs(scroll_speed) < 0.01) { scroll_speed = 0.0; scroll = std::round(scroll); /* Better image quality! */ } else { scroll += scroll_speed; } } min_page_center_dist = 9999999.0; page_closest_to_center = -1; int page_i = 0; for(auto &page_data : image_data) { if(page_data) { if(page_data->image_status == ImageStatus::LOADED && page_data->image->getSize().x > 0 && page_data->image->getSize().y > 0) { if(page_data->texture.loadFromImage(*page_data->image)) { //page_data->texture.setSmooth(true); //page_data->texture.generateMipmap(); double height_before = get_page_size(page_i).y; page_data->image_status = ImageStatus::APPLIED_TO_TEXTURE; page_data->sprite.setTexture(page_data->texture, true); page_size[page_i].size = get_page_size(page_i); page_size[page_i].loaded = true; double height_after = page_size[page_i].size.y; double height_diff = height_before - height_after; if(scroll_speed <= 0.0 && page_i < current_page) { scroll -= height_diff; } } else { page_data->image_status = ImageStatus::FAILED_TO_LOAD; } page_data->image.reset(); } page_data->visible_on_screen = false; } ++page_i; } 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(content_title + " | " + 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: Unloaded page %d\n", 1 + i); page_data.reset(); } ++i; } return ImageViewerAction::NONE; } 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] || image_data[page]->image_status != ImageStatus::APPLIED_TO_TEXTURE) return no_image_page_size; sf::Vector2u texture_size = image_data[page]->texture.getSize(); return sf::Vector2(texture_size.x, texture_size.y); } }