From ce00471fd84745dcbbcb9f2d9ef434acb3d0fd2b Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 14 Mar 2022 18:41:31 +0100 Subject: Image scroll view: add first/last image to allow scrolling to previous/next chapter, load next chapters page when reaching bottom --- README.md | 8 ++-- TODO | 1 - include/ImageViewer.hpp | 4 ++ src/ImageViewer.cpp | 123 ++++++++++++++++++++++++++++++++---------------- src/QuickMedia.cpp | 64 +++++++++++++++++-------- src/plugins/Youtube.cpp | 1 + 6 files changed, 135 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 2d88ade..db67a42 100644 --- a/README.md +++ b/README.md @@ -88,16 +88,14 @@ Type text and then wait and QuickMedia will automatically search.\ `Page down`: Go forward 10 pages.\ `Home`: Go to the first page.\ `End`: Go to the last page.\ -`Ctrl+Arrow up`/`Ctrl+Alt+K`: Go to the previous chapter.\ -`Ctrl+Arrow down`/`Ctrl+Alt+J`: Go to the next chapter.\ `F`: Toggle between scaling the image to the window size or only down scaling if the image is too large.\ `I`: Switch to scroll view. ### Manga scroll view controls `Arrow up`/`Ctrl+K`: Move up.\ `Arrow down`/`Ctrl+J`: Move down.\ -`Mouse scroll`/`Middle mouse click+Move mouse`: Smoothly scroll.\ -`Ctrl+Arrow up`/`Ctrl+Alt+K`: Go to the previous chapter.\ -`Ctrl+Arrow down`/`Ctrl+Alt+J`: Go to the next chapter.\ +`Mouse scroll`/`Middle mouse click+Move mouse`: Scroll smoothly.\ +`Home`: Go to the first page.\ +`End`: Go to the last page.\ `F`: Toggle between scaling the image to the window size or only down scaling if the image is too large.\ `I`: Switch to page view. ### Manga chapters controls diff --git a/TODO b/TODO index a530f0e..c78db7a 100644 --- a/TODO +++ b/TODO @@ -143,7 +143,6 @@ Ctrl+arrow key to move to previous/next video. Add keybinding to view file-manager images in fullscreen to preview them. Also add keybinding to create/delete directories. Reload youtube video url if the video is idle for too long. The video url is only valid for a specific amount of time (the valid duration is in the json). Improve live stream startup time by downloading the video formats in parts instead of the hls/dash manifest? (use YoutubeLiveStreamMediaProxy). -Load the next page in chapter list when reaching the bottom (when going to previous chapters in image view). Loading image background should be rounded. Better deal with reading from file errors. This could happen when reading a file while its being modified. See read_file_as_json. Allow ctrl+r for video when the video is loading. diff --git a/include/ImageViewer.hpp b/include/ImageViewer.hpp index cb63229..b587b82 100644 --- a/include/ImageViewer.hpp +++ b/include/ImageViewer.hpp @@ -55,6 +55,7 @@ namespace QuickMedia { int get_focused_page() const; int get_num_pages() const { return num_pages; } private: + void scroll_to_page(int page); void load_image_async(const Path &path, std::shared_ptr image_data); bool render_page(mgl::Window &window, int page, double offset_y); mgl::vec2d get_page_size(int page); @@ -100,5 +101,8 @@ namespace QuickMedia { bool loading_image = false; bool *fit_image_to_window; + + mgl::vec2d prev_size_first_page; + mgl::vec2d prev_size_last_page; }; } \ No newline at end of file diff --git a/src/ImageViewer.cpp b/src/ImageViewer.cpp index 1176046..064afa4 100644 --- a/src/ImageViewer.cpp +++ b/src/ImageViewer.cpp @@ -4,6 +4,7 @@ #include "../include/ResourceLoader.hpp" #include "../include/Scale.hpp" #include "../include/Config.hpp" +#include "../include/Theme.hpp" #include #include #include @@ -83,28 +84,49 @@ namespace QuickMedia { } bool ImageViewer::render_page(mgl::Window &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 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); - 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 = floor(render_pos.y); double top_dist = std::abs(0.0 - render_pos.y); - if(top_dist < min_page_top_dist) { + 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; @@ -140,14 +162,14 @@ namespace QuickMedia { 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(mgl::Color(0, 0, 0, 255)); + 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(mgl::Color(255, 255, 255, 255)); + background.set_color(get_theme().selected_color); background.set_position(mgl::vec2f(render_pos.x, render_pos.y)); window.draw(background); @@ -159,14 +181,14 @@ namespace QuickMedia { 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(mgl::Color(0, 0, 0, 255)); + 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(mgl::Color(255, 255, 255, 255)); + background.set_color(get_theme().selected_color); background.set_position(mgl::vec2f(render_pos.x, render_pos.y)); window.draw(background); @@ -202,12 +224,30 @@ namespace QuickMedia { return (!key.control && key.code == mgl::Keyboard::Down) || (!key.alt && key.control && key.code == mgl::Keyboard::J); } - static bool is_key_previous_chapter(const mgl::Event::KeyEvent &key) { - return (key.control && key.code == mgl::Keyboard::Up) || (key.alt && key.control && key.code == mgl::Keyboard::K); - } + void ImageViewer::scroll_to_page(int page) { + scroll = 0.0; + scroll_speed = 0.0; - static bool is_key_next_chapter(const mgl::Event::KeyEvent &key) { - return (key.control && key.code == mgl::Keyboard::Down) || (key.alt && key.control && key.code == mgl::Keyboard::J); + 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() { @@ -222,15 +262,7 @@ namespace QuickMedia { window_size.x = window_size_i.x; window_size.y = window_size_i.y; window_size_set = true; - - for(int i = 0; i < num_pages; ++i) { - const mgl::vec2d current_page_size = get_page_size(i); - const double scroll_diff = current_page_size.y - page_size[i].prev_size.y; - page_size[i].prev_size = current_page_size; - - if(i < current_page) - scroll -= scroll_diff; - } + scroll_to_page(current_page); } // TODO: Only redraw when scrolling and when image has finished downloading @@ -250,18 +282,18 @@ namespace QuickMedia { if(is_key_scroll_down(event.key)) down_pressed = true; - if(is_key_previous_chapter(event.key)) - return ImageViewerAction::PREVIOUS_CHAPTER; - - if(is_key_next_chapter(event.key)) - return ImageViewerAction::NEXT_CHAPTER; - 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) { @@ -347,10 +379,19 @@ namespace QuickMedia { } double page_offset = 0.0; - for(int i = 0; i < num_pages; ++i) { + for(int i = -1; i < num_pages + 1; ++i) { const mgl::vec2d current_page_size = get_page_size(i); - const double scroll_diff = current_page_size.y - page_size[i].prev_size.y; - page_size[i].prev_size = current_page_size; + 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; @@ -359,8 +400,8 @@ namespace QuickMedia { page_offset += current_page_size.y; } - const double first_image_height = get_page_size(0).y; - const double last_image_height = get_page_size((int)image_data.size() - 1).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 @@ -368,8 +409,10 @@ namespace QuickMedia { 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) @@ -410,7 +453,7 @@ namespace QuickMedia { } int ImageViewer::get_focused_page() const { - return 1 + focused_page; + return std::max(0, std::min(1 + focused_page, num_pages)); } mgl::vec2d ImageViewer::get_page_size(int page) { diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index e6e8e68..4bdf4d6 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -2210,18 +2210,50 @@ namespace QuickMedia { page_navigation = image_continuous_page(manga_images_page); } - if(page_navigation == -1) { + if(page_navigation == -1) { // previous chapter // TODO: Make this work if the list is sorted differently than from newest to oldest. - chapters_body->select_next_item(); - select_episode(chapters_body->get_selected(), !continue_left_off); - if(!continue_left_off) - image_index = 99999; // Start at the page that shows we are at the end of the chapter - manga_images_page->change_chapter(chapters_body->get_selected()->get_title(), chapters_body->get_selected()->url); - } else if(page_navigation == 1) { + + if(chapters_body->select_next_item()) { + select_episode(chapters_body->get_selected(), !continue_left_off); + if(!continue_left_off) + image_index = 99999; // Start at the page that shows we are at the end of the chapter + manga_images_page->change_chapter(chapters_body->get_selected()->get_title(), chapters_body->get_selected()->url); + } else if(!tab_associated_data[selected_tab].fetching_next_page_failed) { + BodyItems new_body_items; + const int fetch_page = tab_associated_data[selected_tab].fetched_page + 1; + TaskResult load_next_page_result = run_task_with_loading_screen([&] { + if(tabs[selected_tab].page->get_page("", fetch_page, new_body_items) != PluginResult::OK) { + fprintf(stderr, "Failed to get next page (page %d)\n", fetch_page); + return false; + } + return true; + }); + + fprintf(stderr, "Finished fetching page %d, num new items: %zu\n", fetch_page, new_body_items.size()); + size_t num_new_messages = new_body_items.size(); + if(num_new_messages > 0) { + tabs[selected_tab].body->append_items(new_body_items); + tab_associated_data[selected_tab].fetched_page++; + + select_episode(chapters_body->get_selected(), !continue_left_off); + if(!continue_left_off) + image_index = 99999; // Start at the page that shows we are at the end of the chapter + manga_images_page->change_chapter(chapters_body->get_selected()->get_title(), chapters_body->get_selected()->url); + } else { + tab_associated_data[selected_tab].fetching_next_page_failed = true; + } + + if(load_next_page_result == TaskResult::CANCEL) { + current_page = pop_page_stack(); + break; + } + } + } else if(page_navigation == 1) { // next chapter // TODO: Make this work if the list is sorted differently than from newest to oldest. - chapters_body->select_previous_item(); - select_episode(chapters_body->get_selected(), !continue_left_off); - manga_images_page->change_chapter(chapters_body->get_selected()->get_title(), chapters_body->get_selected()->url); + if(chapters_body->select_previous_item()) { + select_episode(chapters_body->get_selected(), !continue_left_off); + manga_images_page->change_chapter(chapters_body->get_selected()->get_title(), chapters_body->get_selected()->url); + } } } } @@ -3118,7 +3150,6 @@ namespace QuickMedia { std::string audio_url; bool has_embedded_audio = true; - std::string prev_start_time; std::vector media_chapters; auto load_video_error_check = [&](std::string start_time = "", bool reuse_media_source = false) mutable { @@ -3219,7 +3250,6 @@ namespace QuickMedia { const bool is_resume_go_back = !start_time.empty(); if(start_time.empty()) start_time = video_page->get_url_timestamp(); - prev_start_time = start_time; watched_videos.insert(video_page->get_url()); // TODO: Sync sequences @@ -4081,12 +4111,6 @@ namespace QuickMedia { image_index = num_manga_pages; goto end_of_images_page; } - } else if((event.key.control && event.key.code == mgl::Keyboard::Up) || (event.key.alt && event.key.control && event.key.code == mgl::Keyboard::K)) { - page_navigation = -1; - goto end_of_images_page; - } else if((event.key.control && event.key.code == mgl::Keyboard::Down) || (event.key.alt && event.key.control && event.key.code == mgl::Keyboard::J)) { - page_navigation = 1; - goto end_of_images_page; } else if(event.key.code == mgl::Keyboard::Escape) { current_page = pop_page_stack(); } else if(event.key.code == mgl::Keyboard::I) { @@ -4200,7 +4224,7 @@ namespace QuickMedia { Json::Value json_chapter; int current_read_page; save_manga_progress(images_page, json_chapters, json_chapter, current_read_page); - ImageViewer image_viewer(&window, num_manga_pages, images_page->manga_name, images_page->get_chapter_name(), image_index, content_cache_dir, &fit_image_to_window); + ImageViewer image_viewer(&window, num_manga_pages, images_page->manga_name, images_page->get_chapter_name(), std::min(num_manga_pages - 1, image_index), content_cache_dir, &fit_image_to_window); idle_active_handler(); @@ -4234,7 +4258,7 @@ namespace QuickMedia { image_index = focused_page - 1; if(focused_page != current_read_page) { current_read_page = focused_page; - json_chapter["current"] = current_read_page; + json_chapter["current"] = std::max(0, std::min(current_read_page, num_manga_pages)); json_chapters[images_page->get_chapter_name()] = json_chapter; content_storage_json["chapters"] = json_chapters; if(!save_json_to_file_atomic(content_storage_file, content_storage_json)) { diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index b3150a3..bbf3108 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -1869,6 +1869,7 @@ namespace QuickMedia { return ""; } + // TODO: Remove very old videos, to not make this file too large which slows this down on slow harddrives std::unordered_map watch_progress = get_watch_progress_for_plugin("youtube"); auto it = watch_progress.find(video_id); if(it == watch_progress.end()) -- cgit v1.2.3