#include "../include/ImageViewer.hpp" #include "../include/Notification.hpp" #include "../include/Storage.hpp" #include "../include/ResourceLoader.hpp" #include "../include/Scale.hpp" #include "../include/Config.hpp" #include "../include/Theme.hpp" #include #include #include #include #include #include #include namespace QuickMedia { static const int page_text_character_size = 14 * get_config().scale * get_config().font_scale; static mgl::vec2d get_no_image_size_scaled(mgl::vec2d window_size, bool fit_image_to_window) { mgl::vec2d no_image_page_size(720.0, 1280.0); mgl::vec2f no_image_page_size_f(no_image_page_size.x, no_image_page_size.y); mgl::vec2f content_size(window_size.x, window_size.y); mgl::vec2f image_scale; if(fit_image_to_window) image_scale = get_ratio(no_image_page_size_f, wrap_to_size_x(no_image_page_size_f, content_size.x)); else image_scale = get_ratio(no_image_page_size_f, clamp_to_size_x(no_image_page_size_f, content_size.x)); no_image_page_size.x *= image_scale.x; no_image_page_size.y *= image_scale.y; return no_image_page_size; } ImageViewer::ImageViewer(mgl::Window *window, int num_pages, const std::string &content_title, const std::string &chapter_title, int current_page, const Path &chapter_cache_dir, bool *fit_image_to_window) : window(window), current_page(current_page), num_pages(num_pages), content_title(content_title), chapter_title(chapter_title), chapter_cache_dir(chapter_cache_dir), focused_page(current_page), page_text("", *FontLoader::get_font(FontLoader::FontType::LATIN, page_text_character_size)), fit_image_to_window(fit_image_to_window) { 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.set_color(mgl::Color(255, 255, 255, 255)); // TODO: Fix //has_default_cursor = default_cursor.loadFromSystem(sf::Cursor::Arrow); //has_size_vertical_cursor = size_vertical_cursor.loadFromSystem(sf::Cursor::SizeVertical); } ImageViewer::~ImageViewer() { // TODO: Fix //if(has_default_cursor) // window->setMouseCursor(default_cursor); if(image_loader_thread.joinable()) image_loader_thread.join(); } void ImageViewer::load_image_async(const Path &path, std::shared_ptr image_data) { image_data->image_status = ImageStatus::LOADING; assert(!loading_image); loading_image = true; if(image_loader_thread.joinable()) image_loader_thread.join(); image_loader_thread = std::thread([this, image_data, path]() mutable { auto image = std::make_unique(); if(image->load_from_file(path.data.c_str())) { image_data->image = std::move(image); image_data->image_status = ImageStatus::LOADED; } else { image_data->image_status = ImageStatus::FAILED_TO_LOAD; } loading_image = false; }); } bool ImageViewer::render_page(mgl::Window &window, int page, double offset_y) { const mgl::vec2d image_size = get_page_size(page); mgl::vec2d render_pos(floor(window_size.x * 0.5 - image_size.x * 0.5), scroll + offset_y); bool scrolling = (std::abs(scroll_speed) > 0.01f); if(!scrolling) render_pos.y = floor(render_pos.y); double top_dist = std::abs(0.0 - render_pos.y); if(top_dist < min_page_top_dist && page != -1 && page != num_pages) { min_page_top_dist = top_dist; page_closest_to_top = page; } if(render_pos.y + image_size.y <= 0.0 || render_pos.y >= window_size.y) { if(page >= 0 && page < num_pages) { std::shared_ptr &page_image_data = image_data[page]; if(page_image_data) page_image_data->visible_on_screen = false; } return true; } if(page == -1 || page == num_pages) { // TODO: Dont show if first/last chapter mgl::Text text(page == -1 ? "Scroll up to go to the previous chapter" : "Scroll down to go to the next chapter", *FontLoader::get_font(FontLoader::FontType::LATIN, 30 * get_config().scale * get_config().font_scale)); auto text_bounds = text.get_bounds(); text.set_color(get_theme().text_color); mgl::vec2d render_pos_text(floor(window_size.x * 0.5 - text_bounds.size.x * 0.5), image_size.y * 0.5 - text_bounds.size.y * 0.5 + scroll + offset_y); if(!scrolling) render_pos_text.y = floor(render_pos_text.y); mgl::Rectangle background(mgl::vec2f(image_size.x, image_size.y)); background.set_color(get_theme().selected_color); background.set_position(mgl::vec2f(render_pos.x, render_pos.y)); window.draw(background); text.set_position(mgl::vec2f(render_pos_text.x, render_pos_text.y)); window.draw(text); return true; } std::shared_ptr &page_image_data = image_data[page]; if(page_image_data) { page_image_data->visible_on_screen = true; if(page_image_data->image_status == ImageStatus::APPLIED_TO_TEXTURE) { auto texture_size = page_image_data->sprite.get_texture()->get_size(); mgl::vec2f texture_size_f(texture_size.x, texture_size.y); mgl::vec2f content_size(window_size.x, window_size.y); mgl::vec2f image_scale; if(*fit_image_to_window) image_scale = get_ratio(texture_size_f, wrap_to_size_x(texture_size_f, content_size.x)); else image_scale = get_ratio(texture_size_f, clamp_to_size_x(texture_size_f, content_size.x)); page_image_data->sprite.set_scale(image_scale); page_image_data->sprite.set_position(mgl::vec2f(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; } mgl::Text error_message(std::move(msg), *FontLoader::get_font(FontLoader::FontType::LATIN, 30 * get_config().scale * get_config().font_scale)); auto text_bounds = error_message.get_bounds(); error_message.set_color(get_theme().text_color); mgl::vec2d render_pos_text(floor(window_size.x * 0.5 - text_bounds.size.x * 0.5), image_size.y * 0.5 - text_bounds.size.y * 0.5 + scroll + offset_y); if(!scrolling) render_pos_text.y = floor(render_pos_text.y); mgl::Rectangle background(mgl::vec2f(image_size.x, image_size.y)); background.set_color(get_theme().selected_color); background.set_position(mgl::vec2f(render_pos.x, render_pos.y)); window.draw(background); error_message.set_position(mgl::vec2f(render_pos_text.x, render_pos_text.y)); window.draw(error_message); } } else { std::string page_str = std::to_string(1 + page); mgl::Text error_message("Downloading page " + page_str, *FontLoader::get_font(FontLoader::FontType::LATIN, 30 * get_config().scale * get_config().font_scale)); auto text_bounds = error_message.get_bounds(); error_message.set_color(get_theme().text_color); mgl::vec2d render_pos_text(floor(window_size.x * 0.5 - text_bounds.size.x * 0.5), image_size.y * 0.5 - text_bounds.size.y * 0.5 + scroll + offset_y); if(!scrolling) render_pos_text.y = floor(render_pos_text.y); mgl::Rectangle background(mgl::vec2f(image_size.x, image_size.y)); background.set_color(get_theme().selected_color); background.set_position(mgl::vec2f(render_pos.x, render_pos.y)); window.draw(background); error_message.set_position(mgl::vec2f(render_pos_text.x, render_pos_text.y)); window.draw(error_message); Path image_path = chapter_cache_dir; image_path.join(page_str); // TODO: Add delay before checking again? 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; } } return true; } static double sign(double value) { return value >= 0.0 ? 1.0 : -1.0; } static bool is_key_scroll_up(const mgl::Event::KeyEvent &key) { return (!key.control && key.code == mgl::Keyboard::Up) || (!key.alt && key.control && key.code == mgl::Keyboard::K); } static bool is_key_scroll_down(const mgl::Event::KeyEvent &key) { return (!key.control && key.code == mgl::Keyboard::Down) || (!key.alt && key.control && key.code == mgl::Keyboard::J); } void ImageViewer::scroll_to_page(int page) { scroll = 0.0; scroll_speed = 0.0; for(int i = -1; i < num_pages + 1; ++i) { const mgl::vec2d current_page_size = get_page_size(i); double scroll_diff = 0.0; if(i < 0) { scroll_diff = current_page_size.y - prev_size_first_page.y; prev_size_first_page = current_page_size; } else if(i >= num_pages) { scroll_diff = current_page_size.y - prev_size_last_page.y; prev_size_last_page = current_page_size; } else { scroll_diff = current_page_size.y - page_size[i].prev_size.y; page_size[i].prev_size = current_page_size; } if(i < page) { scroll -= scroll_diff; if(scroll_diff < 0.001) scroll -= current_page_size.y; } } } ImageViewerAction ImageViewer::draw() { const double frame_delta = frame_timer.restart(); const double scroll_speed_key_input = 200.0; const double scroll_speed_mouse_wheel = 800.0; const double scroll_speed_autoscroll = 10.0; const double scroll_deaccel = 6.0; // higher value = faster stop if(!window_size_set) { auto window_size_i = window->get_size(); window_size.x = window_size_i.x; window_size.y = window_size_i.y; window_size_set = true; scroll_to_page(current_page); } // TODO: Only redraw when scrolling and when image has finished downloading mgl::Event event; while(window->poll_event(event)) { if(event.type == mgl::Event::KeyPressed) { if(event.key.code == mgl::Keyboard::Q && event.key.control) window->close(); } if(event.type == mgl::Event::Resized) { window_size.x = event.size.width; window_size.y = event.size.height; } else if(event.type == mgl::Event::KeyPressed) { if(is_key_scroll_up(event.key)) up_pressed = true; if(is_key_scroll_down(event.key)) down_pressed = true; if(event.key.code == mgl::Keyboard::Escape) return ImageViewerAction::RETURN; if(event.key.code == mgl::Keyboard::I) return ImageViewerAction::SWITCH_TO_SINGLE_IMAGE_MODE; if(event.key.code == mgl::Keyboard::Home) scroll_to_page(0); if(event.key.code == mgl::Keyboard::End) scroll_to_page(num_pages - 1); if(event.key.code == mgl::Keyboard::F) *fit_image_to_window = !*fit_image_to_window; } else if(event.type == mgl::Event::KeyReleased) { if(is_key_scroll_up(event.key)) up_pressed = false; if(is_key_scroll_down(event.key)) down_pressed = false; } else if(event.type == mgl::Event::MouseWheelScrolled/* && event.mouse_wheel_scroll.wheel == mgl::Mouse::VerticalWheel*/) { scroll_speed += scroll_speed_mouse_wheel * event.mouse_wheel_scroll.delta * frame_delta; } else if(event.type == mgl::Event::MouseButtonPressed && event.mouse_button.button == mgl::Mouse::Button::Middle) { middle_mouse_scrolling = true; autoscroll_start_y = window->get_mouse_position().y; // TODO: Fix //if(has_size_vertical_cursor) // window->setMouseCursor(size_vertical_cursor); } else if(event.type == mgl::Event::MouseButtonReleased && event.mouse_button.button == mgl::Mouse::Button::Middle) { middle_mouse_scrolling = false; scroll_speed = 0.0; // TODO: Fix //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)window->get_mouse_position().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 *= (1.0 - std::min(1.0, scroll_deaccel * frame_delta)); if(std::abs(scroll_speed) < 0.01) { scroll_speed = 0.0; scroll = std::round(scroll); /* Better image quality! */ } else { scroll += scroll_speed; } } min_page_top_dist = 9999999.0; page_closest_to_top = -1; bool loaded_textures_changed = false; int page_i = 0; for(auto &page_data : image_data) { if(page_data) { if(page_data->image_status == ImageStatus::LOADED && page_data->image->get_size().x > 0 && page_data->image->get_size().y > 0) { if(page_data->texture.load_from_image(*page_data->image)) { //page_data->texture.generateMipmap(); page_data->image_status = ImageStatus::APPLIED_TO_TEXTURE; page_data->sprite.set_texture(&page_data->texture); mgl::vec2i texture_size = image_data[page_i]->texture.get_size(); page_size[page_i].size = mgl::vec2d(texture_size.x, texture_size.y); page_size[page_i].loaded = true; } else { page_data->image_status = ImageStatus::FAILED_TO_LOAD; } page_data->image.reset(); loaded_textures_changed = true; } page_data->visible_on_screen = false; } ++page_i; } double page_offset = 0.0; for(int i = -1; i < num_pages + 1; ++i) { const mgl::vec2d current_page_size = get_page_size(i); double scroll_diff = 0.0; if(i < 0) { scroll_diff = current_page_size.y - prev_size_first_page.y; prev_size_first_page = current_page_size; } else if(i >= num_pages) { scroll_diff = current_page_size.y - prev_size_last_page.y; prev_size_last_page = current_page_size; } else { scroll_diff = current_page_size.y - page_size[i].prev_size.y; page_size[i].prev_size = current_page_size; } if(page_offset < -scroll) scroll -= scroll_diff; render_page(*window, i, page_offset); page_offset += current_page_size.y; } const double first_image_height = get_page_size(-1).y; const double last_image_height = get_page_size(image_data.size()).y; // TODO: Do not allow scrolling if all images height (page_offset) is smaller than window height const double top_scroll = std::max(0.0, -first_image_height + window_size.y); const double bottom_scroll = std::min(window_size.y, window_size.y + last_image_height - window_size.y); if(scroll > top_scroll) { scroll = top_scroll; return ImageViewerAction::PREVIOUS_CHAPTER; } else if(scroll + page_offset < bottom_scroll && page_offset > window_size.y) { scroll = -page_offset + bottom_scroll; return ImageViewerAction::NEXT_CHAPTER; } if(page_closest_to_top != -1) focused_page = page_closest_to_top; if(focused_page != prev_focused_page) { prev_focused_page = focused_page; page_text.set_string(content_title + " | " + chapter_title + " | Page " + std::to_string(1 + focused_page) + "/" + std::to_string(num_pages)); } const float font_height = page_text_character_size + 8.0f; const float background_height = font_height + 6.0f; mgl::Rectangle page_text_background(mgl::vec2f(window_size.x, background_height)); page_text_background.set_color(mgl::Color(0, 0, 0, 150)); page_text_background.set_position(mgl::vec2f(0.0f, window_size.y - background_height)); window->draw(page_text_background); auto page_text_bounds = page_text.get_bounds(); page_text.set_position(mgl::vec2f(floor(window_size.x * 0.5f - page_text_bounds.size.x * 0.5f), floor(window_size.y - background_height * 0.5f - font_height * 0.7f))); 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(); loaded_textures_changed = true; } ++i; } if(loaded_textures_changed) malloc_trim(0); return ImageViewerAction::NONE; } int ImageViewer::get_focused_page() const { return std::max(0, std::min(1 + focused_page, num_pages)); } mgl::vec2d ImageViewer::get_page_size(int page) { mgl::vec2d no_image_page_size = get_no_image_size_scaled(window_size, *fit_image_to_window); if(page < 0 || page >= (int)image_data.size()) return no_image_page_size; if(page_size[page].loaded) { mgl::vec2f texture_size_f(page_size[page].size.x, page_size[page].size.y); mgl::vec2f content_size(window_size.x, window_size.y); mgl::vec2f image_scale; if(*fit_image_to_window) image_scale = get_ratio(texture_size_f, wrap_to_size_x(texture_size_f, content_size.x)); else image_scale = get_ratio(texture_size_f, clamp_to_size_x(texture_size_f, content_size.x)); return mgl::vec2d(page_size[page].size.x * image_scale.x, page_size[page].size.y * image_scale.y); } if(!image_data[page] || image_data[page]->image_status != ImageStatus::APPLIED_TO_TEXTURE) return no_image_page_size; // Do not scale here, because this will be used to set page_size[page].size mgl::vec2i texture_size = image_data[page]->texture.get_size(); return mgl::vec2d(texture_size.x, texture_size.y); } }