diff options
author | dec05eba <dec05eba@protonmail.com> | 2020-09-26 05:27:45 +0200 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2020-09-26 05:43:22 +0200 |
commit | 74b18beed8651c4a7f6daa39102766676605cdc2 (patch) | |
tree | 5a4305a3b439a326f3e48dadd65a174f9a20d9d6 /src | |
parent | d5d462f555ef9d7ce7e001aca5586c19b4d9edc8 (diff) |
Change body scrolling to scroll only when the selected row hits the top/bottom
Diffstat (limited to 'src')
-rw-r--r-- | src/Body.cpp | 379 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 14 |
2 files changed, 217 insertions, 176 deletions
diff --git a/src/Body.cpp b/src/Body.cpp index 2bd5499..c849aea 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -4,15 +4,17 @@ #include "../include/base64_url.hpp" #include "../include/ImageUtils.hpp" #include "../plugins/Plugin.hpp" -#include "../external/RoundedRectangleShape.hpp" -#include <SFML/Graphics/RectangleShape.hpp> -#include <SFML/Graphics/Sprite.hpp> #include <SFML/OpenGL.hpp> #include <assert.h> #include <cmath> const sf::Color front_color(32, 36, 42); const sf::Color back_color(33, 35, 37); +float image_max_height = 100.0f; +const float spacing_y = 15.0f; +const float padding_x = 10.0f; +const float image_padding_x = 5.0f; +const float padding_y = 5.0f; namespace QuickMedia { BodyItem::BodyItem(std::string _title) : @@ -69,7 +71,10 @@ namespace QuickMedia { line_seperator_color(sf::Color(32, 37, 43, 255)), program(program), loading_thumbnail(false), - selected_item(0) + selected_item(0), + prev_selected_item(0), + page_scroll(0.0f), + item_background(sf::Vector2f(1.0f, 1.0f), 10.0f, 10) { progress_text.setFillColor(sf::Color::White); replies_text.setFillColor(sf::Color(129, 162, 190)); @@ -77,6 +82,9 @@ namespace QuickMedia { thumbnail_resize_target_size.y = 119; thumbnail_fallback_size.x = 50.0f; thumbnail_fallback_size.y = 100.0f; + image_fallback.setSize(thumbnail_fallback_size); + image_fallback.setFillColor(sf::Color::White); + item_background.setFillColor(sf::Color(55, 60, 68)); } bool Body::select_previous_item() { @@ -138,10 +146,14 @@ namespace QuickMedia { void Body::set_selected_item(int item) { assert(item >= 0 && item < (int)items.size()); selected_item = item; + prev_selected_item = selected_item; + page_scroll = 0.0f; } void Body::select_first_item() { selected_item = 0; + prev_selected_item = selected_item; + page_scroll = 0.0f; clamp_selection(); } @@ -153,11 +165,15 @@ namespace QuickMedia { } } selected_item = 0; + prev_selected_item = selected_item; + page_scroll = 0.0f; } void Body::clear_items() { items.clear(); selected_item = 0; + prev_selected_item = selected_item; + page_scroll = 0.0f; } // TODO: Optimize with memcpy and changing capacity before loop @@ -197,16 +213,20 @@ namespace QuickMedia { for(int i = selected_item; i >= 0; --i) { if(items[i]->visible) { selected_item = i; - return; + goto reset_scroll; } } for(int i = selected_item; i < num_items; ++i) { if(items[i]->visible) { selected_item = i; - return; + goto reset_scroll; } } + + reset_scroll: + prev_selected_item = selected_item; + page_scroll = 0.0f; } static sf::Vector2f to_vec2f(const sf::Vector2u &vec) { @@ -345,34 +365,27 @@ namespace QuickMedia { void Body::draw(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size, const Json::Value &content_progress) { sf::Vector2f scissor_pos = pos; sf::Vector2f scissor_size = size; - - float image_max_height = 100.0f; - const float spacing_y = 15.0f; - const float padding_x = 10.0f; - const float image_padding_x = 5.0f; - const float padding_y = 5.0f; const float start_y = pos.y; - sf::RectangleShape image_fallback(thumbnail_fallback_size); - image_fallback.setFillColor(sf::Color::White); - if(thumbnail_resize_target_size.x != 0 && thumbnail_resize_target_size.y != 0) { image_max_height = thumbnail_resize_target_size.y; } - sf::Sprite image; - - sf::RoundedRectangleShape item_background(sf::Vector2f(1.0f, 1.0f), 10.0f, 10); //item_background.setFillColor(front_color); - item_background.setFillColor(sf::Color(55, 60, 68)); //item_background.setOutlineThickness(1.0f); //item_background.setOutlineColor(sf::Color(13, 15, 17)); - sf::RectangleShape item_background_shadow; item_background_shadow.setFillColor(line_seperator_color); int num_items = items.size(); - if(num_items == 0) + if(num_items == 0 || size.y <= 0.0f) { + for(auto it = item_thumbnail_textures.begin(); it != item_thumbnail_textures.end();) { + if(!it->second.referenced) + it = item_thumbnail_textures.erase(it); + else + ++it; + } return; + } for(auto &thumbnail_it : item_thumbnail_textures) { thumbnail_it.second.referenced = false; @@ -413,189 +426,217 @@ namespace QuickMedia { } } - // Find the starting row that can be drawn to make selected row visible as well - int first_visible_item = selected_item; - assert(first_visible_item >= 0 && first_visible_item < (int)items.size()); - float visible_height = 0.0f; - for(; first_visible_item >= 0; --first_visible_item) { - auto &item = items[first_visible_item]; - if(item->visible) { - float item_height = 0.0f; - if(!item->get_title().empty()) { - item_height += item->title_text->getHeight() - 2.0f; - } - if(!item->get_author().empty()) { - item_height += item->author_text->getHeight() - 2.0f; - } - if(item->description_text) { - item_height += item->description_text->getHeight() - 2.0f; - } - if(draw_thumbnails && !item->thumbnail_url.empty()) { - auto &item_thumbnail = item_thumbnail_textures[item->thumbnail_url]; - item_thumbnail.referenced = false; - float image_height = image_fallback.getSize().y; - if(item_thumbnail.texture && item_thumbnail.texture->getNativeHandle() != 0) { - auto image_size = item_thumbnail.texture->getSize(); - image_height = std::min(image_max_height, (float)image_size.y); - } - item_height = std::max(item_height, image_height); + // TODO: Optimize this, especially when scrolling to top/bottom. + // TODO: Test when wrapping is enabled + int selected_item_diff = selected_item - prev_selected_item; + int selected_int_diff_abs = std::abs(selected_item_diff); + if(selected_item_diff > 0) { + int num_items_scrolled = 0; + int i = prev_selected_item; + while(num_items_scrolled < selected_int_diff_abs && i < num_items) { + if(items[i]->visible) { + page_scroll += (get_item_height(items[i].get()) + spacing_y); } - item_height += (spacing_y + padding_y * 2.0f); - visible_height += item_height; - if(visible_height >= size.y) { - --first_visible_item; - //pos.y += (size.y - (visible_height - item_height)); - pos.y -= (visible_height - size.y); - break; + ++num_items_scrolled; + ++i; + } + prev_selected_item = selected_item; + } else if(selected_item_diff < 0) { + int num_items_scrolled = 0; + int i = prev_selected_item - 1; + while(num_items_scrolled < selected_int_diff_abs && i >= 0) { + if(items[i]->visible) { + page_scroll -= (get_item_height(items[i].get()) + spacing_y); } + ++num_items_scrolled; + --i; } + prev_selected_item = selected_item; + } + + float selected_item_height = get_item_height(items[selected_item].get()) + spacing_y; + if(page_scroll > size.y - selected_item_height) { + page_scroll = size.y - selected_item_height; + } else if(page_scroll < 0.0f) { + page_scroll = 0.0f; } + pos.y += page_scroll; + sf::Vector2u window_size = window.getSize(); glEnable(GL_SCISSOR_TEST); glScissor(scissor_pos.x, (int)window_size.y - (int)scissor_pos.y - (int)scissor_size.y, scissor_size.x, scissor_size.y); - for(int i = first_visible_item + 1; i < num_items; ++i) { + sf::Vector2f prev_pos = pos; + for(int i = selected_item - 1; i >= 0; --i) { auto &item = items[i]; - if(pos.y >= start_y + size.y) + // TODO: Find a better solution? + if(!item->visible) + continue; + + float item_height = get_item_height(item.get()); + prev_pos.y -= item_height + spacing_y; + + if(prev_pos.y + item_height <= 0.0f) break; + draw_item(window, item.get(), prev_pos, size, item_height, i, content_progress); + } + + sf::Vector2f after_pos = pos; + for(int i = selected_item; i < num_items; ++i) { + auto &item = items[i]; + + // TODO: Find a better solution? if(!item->visible) continue; - // TODO: Instead of generating a new hash everytime to access textures, cache the hash of the thumbnail url - // Intentionally create the item with the key item->thumbnail_url if it doesn't exist - item_thumbnail_textures[item->thumbnail_url].referenced = true; - auto &item_thumbnail = item_thumbnail_textures[item->thumbnail_url]; - - float item_height = 0.0f; - if(!item->get_title().empty()) { - item_height += item->title_text->getHeight() - 2.0f; - } - if(!item->get_author().empty()) { - item_height += item->author_text->getHeight() - 2.0f; - } - if(item->description_text) { - item_height += item->description_text->getHeight() - 2.0f; - } - if(draw_thumbnails && !item->thumbnail_url.empty()) { - float image_height = image_fallback.getSize().y; - if(item_thumbnail.loaded && item_thumbnail.texture && item_thumbnail.texture->getNativeHandle() != 0) { - auto image_size = item_thumbnail.texture->getSize(); - image_height = std::min(image_max_height, (float)image_size.y); - } - item_height = std::max(item_height, image_height); - } - item_height += (padding_y * 2.0f); + if(after_pos.y >= start_y + size.y) + break; - if(draw_thumbnails) { - if(!item->thumbnail_url.empty() && !loading_thumbnail && !item_thumbnail.loaded && !item_thumbnail.texture) { - item_thumbnail.loaded = true; - item_thumbnail.texture = load_thumbnail_from_url(item->thumbnail_url, item->thumbnail_is_local, thumbnail_resize_target_size); - } - } + float item_height = get_item_height(item.get()); + draw_item(window, item.get(), after_pos, size, item_height, i, content_progress); + after_pos.y += item_height + spacing_y; + } - sf::Vector2f item_pos; - item_pos.x = std::floor(pos.x); - item_pos.y = std::floor(pos.y); + glDisable(GL_SCISSOR_TEST); - item_background_shadow.setSize(sf::Vector2f(std::max(0.0f, size.x - 20.0f), 1.0f)); - item_background_shadow.setPosition(item_pos + sf::Vector2f(10.0f, std::floor(item_height + spacing_y * 0.5f))); - window.draw(item_background_shadow); + for(auto it = item_thumbnail_textures.begin(); it != item_thumbnail_textures.end();) { + if(!it->second.referenced) + it = item_thumbnail_textures.erase(it); + else + ++it; + } + } - if(i == selected_item) { - item_background.setPosition(item_pos); - item_background.setSize(sf::Vector2f(size.x, item_height)); - window.draw(item_background); - } + void Body::draw_item(sf::RenderWindow &window, BodyItem *item, const sf::Vector2f &pos, const sf::Vector2f &size, const float item_height, const int item_index, const Json::Value &content_progress) { + // TODO: Instead of generating a new hash everytime to access textures, cache the hash of the thumbnail url + // Intentionally create the item with the key item->thumbnail_url if it doesn't exist + item_thumbnail_textures[item->thumbnail_url].referenced = true; + auto &item_thumbnail = item_thumbnail_textures[item->thumbnail_url]; - float text_offset_x = padding_x; - if(draw_thumbnails) { - // TODO: Verify if this is safe. The thumbnail is being modified in another thread - // and it might not be fully finished before the native handle is set? - if(item_thumbnail.texture && item_thumbnail.texture->getNativeHandle() != 0) { - image.setTexture(*item_thumbnail.texture, true); - auto image_size = image.getTexture()->getSize(); - auto height_ratio = std::min(image_max_height, (float)image_size.y) / image_size.y; - auto scale = image.getScale(); - auto image_scale_ratio = scale.x / scale.y; - const float width_ratio = height_ratio * image_scale_ratio; - image.setScale(width_ratio, height_ratio); - image.setPosition(item_pos + sf::Vector2f(image_padding_x, padding_y)); - window.draw(image); - text_offset_x += image_padding_x + width_ratio * image_size.x; - } else if(!item->thumbnail_url.empty()) { - image_fallback.setPosition(item_pos + sf::Vector2f(image_padding_x, padding_y)); - window.draw(image_fallback); - text_offset_x += image_padding_x + image_fallback.getSize().x; - } + if(draw_thumbnails) { + if(!item->thumbnail_url.empty() && !loading_thumbnail && !item_thumbnail.loaded && !item_thumbnail.texture) { + item_thumbnail.loaded = true; + item_thumbnail.texture = load_thumbnail_from_url(item->thumbnail_url, item->thumbnail_is_local, thumbnail_resize_target_size); } + } - if(!item->get_author().empty()) { - item->author_text->setPosition(std::floor(item_pos.x + text_offset_x), std::floor(item_pos.y + padding_y - 6.0f)); - item->author_text->setMaxWidth(size.x - text_offset_x - image_padding_x * 2.0f); - item->author_text->draw(window); + sf::Vector2f item_pos; + item_pos.x = std::floor(pos.x); + item_pos.y = std::floor(pos.y); - sf::Vector2f replies_text_pos = item->author_text->getPosition() + sf::Vector2f(0.0f, 5.0f); - replies_text_pos.x += item->author_text->getWidth() + 5.0f; - replies_text.setPosition(replies_text_pos); + item_background_shadow.setSize(sf::Vector2f(std::max(0.0f, size.x - 20.0f), 1.0f)); + item_background_shadow.setPosition(item_pos + sf::Vector2f(10.0f, std::floor(item_height + spacing_y * 0.5f))); + window.draw(item_background_shadow); - sf::String replies_text_str; - for(size_t reply_index : item->replies) { - BodyItem *reply_item = items[reply_index].get(); - replies_text_str += ">>"; - replies_text_str += reply_item->post_number; - } - replies_text.setString(std::move(replies_text_str)); - window.draw(replies_text); + if(item_index == selected_item) { + item_background.setPosition(item_pos); + item_background.setSize(sf::Vector2f(size.x, item_height)); + window.draw(item_background); + } - item_pos.y += item->author_text->getHeight() - 2.0f; - } - //title_text.setString(item->title); - //title_text.setPosition(std::floor(item_pos.x + text_offset_x), std::floor(item_pos.y + padding_y)); - //window.draw(title_text); - if(!item->get_title().empty()) { - item->title_text->setFillColor(item->title_color); - item->title_text->setPosition(std::floor(item_pos.x + text_offset_x), std::floor(item_pos.y + padding_y - 8.0f)); - item->title_text->setMaxWidth(size.x - text_offset_x - image_padding_x * 2.0f); - item->title_text->draw(window); + float text_offset_x = padding_x; + if(draw_thumbnails) { + // TODO: Verify if this is safe. The thumbnail is being modified in another thread + // and it might not be fully finished before the native handle is set? + if(item_thumbnail.loaded && item_thumbnail.texture && item_thumbnail.texture->getNativeHandle() != 0) { + image.setTexture(*item_thumbnail.texture, true); + auto image_size = image.getTexture()->getSize(); + auto height_ratio = std::min(image_max_height, (float)image_size.y) / image_size.y; + auto scale = image.getScale(); + auto image_scale_ratio = scale.x / scale.y; + const float width_ratio = height_ratio * image_scale_ratio; + image.setScale(width_ratio, height_ratio); + image.setPosition(item_pos + sf::Vector2f(image_padding_x, padding_y)); + window.draw(image); + text_offset_x += image_padding_x + width_ratio * image_size.x; + // We want the next image fallback to have the same size as the successful image rendering, because its likely the image fallback will have the same size (for example thumbnails on youtube) + image_fallback.setSize(sf::Vector2f(width_ratio * image_size.x, height_ratio * image_size.y)); + } else if(!item->thumbnail_url.empty()) { + image_fallback.setPosition(item_pos + sf::Vector2f(image_padding_x, padding_y)); + window.draw(image_fallback); + text_offset_x += image_padding_x + image_fallback.getSize().x; } + } - if(!item->get_description().empty()) { - float height_offset = 0.0f; - if(!item->get_title().empty()) { - height_offset = item->title_text->getHeight(); - } - item->description_text->setPosition(std::floor(item_pos.x + text_offset_x), std::floor(item_pos.y + padding_y - 8.0f + height_offset)); - item->description_text->setMaxWidth(size.x - text_offset_x - image_padding_x * 2.0f); - item->description_text->draw(window); + if(!item->get_author().empty()) { + item->author_text->setPosition(std::floor(item_pos.x + text_offset_x), std::floor(item_pos.y + padding_y - 6.0f)); + item->author_text->setMaxWidth(size.x - text_offset_x - image_padding_x * 2.0f); + item->author_text->draw(window); + + sf::Vector2f replies_text_pos = item->author_text->getPosition() + sf::Vector2f(0.0f, 5.0f); + replies_text_pos.x += item->author_text->getWidth() + 5.0f; + replies_text.setPosition(replies_text_pos); + + sf::String replies_text_str; + for(size_t reply_index : item->replies) { + BodyItem *reply_item = items[reply_index].get(); + replies_text_str += ">>"; + replies_text_str += reply_item->post_number; } + replies_text.setString(std::move(replies_text_str)); + window.draw(replies_text); - // TODO: Do the same for non-manga content. - // TODO: Cache this instead of hash access every item every frame. - const Json::Value &item_progress = content_progress[item->get_title()]; - if(item_progress.isObject()) { - const Json::Value ¤t_json = item_progress["current"]; - const Json::Value &total_json = item_progress["total"]; - if(current_json.isNumeric() && total_json.isNumeric()) { - progress_text.setString(std::string("Page: ") + std::to_string(current_json.asInt()) + "/" + std::to_string(total_json.asInt())); - auto bounds = progress_text.getLocalBounds(); - progress_text.setPosition(std::floor(item_pos.x + size.x - bounds.width - padding_x), std::floor(item_pos.y + padding_y)); - window.draw(progress_text); - } + item_pos.y += item->author_text->getHeight() - 2.0f; + } + //title_text.setString(item->title); + //title_text.setPosition(std::floor(item_pos.x + text_offset_x), std::floor(item_pos.y + padding_y)); + //window.draw(title_text); + if(!item->get_title().empty()) { + item->title_text->setFillColor(item->title_color); + item->title_text->setPosition(std::floor(item_pos.x + text_offset_x), std::floor(item_pos.y + padding_y - 8.0f)); + item->title_text->setMaxWidth(size.x - text_offset_x - image_padding_x * 2.0f); + item->title_text->draw(window); + } + + if(!item->get_description().empty()) { + float height_offset = 0.0f; + if(!item->get_title().empty()) { + height_offset = item->title_text->getHeight(); } + item->description_text->setPosition(std::floor(item_pos.x + text_offset_x), std::floor(item_pos.y + padding_y - 8.0f + height_offset)); + item->description_text->setMaxWidth(size.x - text_offset_x - image_padding_x * 2.0f); + item->description_text->draw(window); + } - pos.y += item_height + spacing_y; + // TODO: Do the same for non-manga content. + // TODO: Cache this instead of hash access every item every frame. + const Json::Value &item_progress = content_progress[item->get_title()]; + if(item_progress.isObject()) { + const Json::Value ¤t_json = item_progress["current"]; + const Json::Value &total_json = item_progress["total"]; + if(current_json.isNumeric() && total_json.isNumeric()) { + progress_text.setString(std::string("Page: ") + std::to_string(current_json.asInt()) + "/" + std::to_string(total_json.asInt())); + auto bounds = progress_text.getLocalBounds(); + progress_text.setPosition(std::floor(item_pos.x + size.x - bounds.width - padding_x), std::floor(item_pos.y + padding_y)); + window.draw(progress_text); + } } - glDisable(GL_SCISSOR_TEST); + } - for(auto it = item_thumbnail_textures.begin(); it != item_thumbnail_textures.end();) { - if(!it->second.referenced) - it = item_thumbnail_textures.erase(it); - else - ++it; + float Body::get_item_height(BodyItem *item) { + float item_height = 0.0f; + if(!item->get_title().empty()) { + item_height += item->title_text->getHeight() - 2.0f; + } + if(!item->get_author().empty()) { + item_height += item->author_text->getHeight() - 2.0f; + } + if(item->description_text) { + item_height += item->description_text->getHeight() - 2.0f; + } + if(draw_thumbnails && !item->thumbnail_url.empty()) { + auto &item_thumbnail = item_thumbnail_textures[item->thumbnail_url]; + float image_height = image_fallback.getSize().y; + if(item_thumbnail.texture && item_thumbnail.texture->getNativeHandle() != 0) { + auto image_size = item_thumbnail.texture->getSize(); + image_height = std::min(image_max_height, (float)image_size.y); + } + item_height = std::max(item_height, image_height); } + return item_height + padding_y * 2.0f; } //static diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 1039ca7..9300059 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -1040,7 +1040,7 @@ namespace QuickMedia { update_search_text = text; else { tabs[selected_tab].body->filter_search_fuzzy(text); - tabs[selected_tab].body->clamp_selection(); + tabs[selected_tab].body->select_first_item(); } if(tabs[selected_tab].body == recommended_body.get()) recommended_filter = text; @@ -1104,7 +1104,7 @@ namespace QuickMedia { } else if(event.key.code == sf::Keyboard::Left) { if(tabs[selected_tab].body) { tabs[selected_tab].body->filter_search_fuzzy(""); - tabs[selected_tab].body->clamp_selection(); + tabs[selected_tab].body->select_first_item(); tabs[selected_tab].body->clear_thumbnails(); } selected_tab = std::max(0, selected_tab - 1); @@ -1112,7 +1112,7 @@ namespace QuickMedia { } else if(event.key.code == sf::Keyboard::Right) { if(tabs[selected_tab].body) { tabs[selected_tab].body->filter_search_fuzzy(""); - tabs[selected_tab].body->clamp_selection(); + tabs[selected_tab].body->select_first_item(); tabs[selected_tab].body->clear_thumbnails(); } selected_tab = std::min((int)tabs.size() - 1, selected_tab + 1); @@ -1800,7 +1800,7 @@ namespace QuickMedia { search_bar->onTextUpdateCallback = [&tabs, &selected_tab](const std::string &text) { tabs[selected_tab].body->filter_search_fuzzy(text); - tabs[selected_tab].body->clamp_selection(); + tabs[selected_tab].body->select_first_item(); }; search_bar->onTextSubmitCallback = [this, &tabs, &selected_tab, &json_chapters](const std::string&) -> bool { @@ -1886,13 +1886,13 @@ namespace QuickMedia { search_bar->clear(); } else if(event.key.code == sf::Keyboard::Left) { tabs[selected_tab].body->filter_search_fuzzy(""); - tabs[selected_tab].body->clamp_selection(); + tabs[selected_tab].body->select_first_item(); tabs[selected_tab].body->clear_thumbnails(); selected_tab = std::max(0, selected_tab - 1); search_bar->clear(); } else if(event.key.code == sf::Keyboard::Right) { tabs[selected_tab].body->filter_search_fuzzy(""); - tabs[selected_tab].body->clamp_selection(); + tabs[selected_tab].body->select_first_item(); tabs[selected_tab].body->clear_thumbnails(); selected_tab = std::min((int)tabs.size() - 1, selected_tab + 1); search_bar->clear(); @@ -1934,7 +1934,7 @@ namespace QuickMedia { { tab.body->items = tab.creator_page_download_future.get(); tab.body->filter_search_fuzzy(search_bar->get_text()); - tab.body->clamp_selection(); + tab.body->select_first_item(); } if(i == selected_tab) { |