From 84d4d43d2f79da48c3494e11c8b29790fb6eae12 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 24 Mar 2021 08:42:40 +0100 Subject: Matrix: merge body items if same author --- TODO | 1 - include/Body.hpp | 9 +++-- src/Body.cpp | 103 +++++++++++++++++++++++++++++++++++++++++++---------- src/QuickMedia.cpp | 23 ++++++++++-- 4 files changed, 113 insertions(+), 23 deletions(-) diff --git a/TODO b/TODO index 5798914..3c3ca29 100644 --- a/TODO +++ b/TODO @@ -37,7 +37,6 @@ Add option to disable autosearch and search when pressing enter instead or somet Sleep when idle, to reduce cpu usage from 1-2% to 0%, important for mobile devices. Also render view to a rendertexture and render that instead of redrawing every time every time. Provide a way to specify when notifications should be received (using matrix api) and also read the notification config from matrix. Also provide a way to disable notifications globally. Use quickmedia to show image in matrix rooms, instead of mpv. -Merge body items in matrix if they are posted by the same author (there is a git stash for this). Add command to ban users. Support peertube (works with mpv, but need to implement search and related videos). Scroll to bottom when receiving a new message even if the selected message is not the last one. It should instead scroll if the last message is visible on the screen. diff --git a/include/Body.hpp b/include/Body.hpp index 53622e8..738c41b 100644 --- a/include/Body.hpp +++ b/include/Body.hpp @@ -160,6 +160,8 @@ namespace QuickMedia { using BodyItems = std::vector>; using BodyItemRenderCallback = std::function; + // Return true to merge + using BodyItemMergeHandler = std::function; enum class AttachSide { TOP, @@ -216,7 +218,7 @@ namespace QuickMedia { // because of Text::setMaxWidth void draw_item(sf::RenderWindow &window, BodyItem *item, sf::Vector2f pos, sf::Vector2f size, bool include_embedded_item = true, bool is_embedded = false); - float get_item_height(BodyItem *item, float width, bool load_texture = true, bool include_embedded_item = true); + float get_item_height(BodyItem *item, float width, bool load_texture = true, bool include_embedded_item = true, bool merge_with_previous = false); float get_spacing_y() const; static bool string_find_case_insensitive(const std::string &str, const std::string &substr); @@ -249,14 +251,17 @@ namespace QuickMedia { sf::Vector2i thumbnail_max_size; sf::Color line_separator_color; BodyItemRenderCallback body_item_render_callback; + BodyItemMergeHandler body_item_merge_handler; std::function body_item_select_callback; sf::Shader *thumbnail_mask_shader; AttachSide attach_side = AttachSide::TOP; private: - void 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, bool include_embedded_item = true); + void 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, bool include_embedded_item = true, bool merge_with_previous = false); void update_dirty_state(BodyItem *body_item, float width); void clear_body_item_cache(BodyItem *body_item); sf::Vector2i get_item_thumbnail_size(BodyItem *item) const; + BodyItem* get_previous_visible_item(int start_index); + BodyItem* get_next_visible_item(int start_index); private: Program *program; std::unordered_map> item_thumbnail_textures; diff --git a/src/Body.cpp b/src/Body.cpp index a34fb7d..ee84f5f 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -534,9 +534,15 @@ namespace QuickMedia { if(selected_item_diff > 0) { int num_items_scrolled = 0; int i = prev_selected_item; + BodyItem *prev_body_item = get_previous_visible_item(i); while(num_items_scrolled < selected_int_diff_abs && i < num_items) { if(items[i]->visible) { - page_scroll += (get_item_height(items[i].get(), size.x, selected_int_diff_abs < 50) + spacing_y); + const bool merge_with_previous = body_item_merge_handler && body_item_merge_handler(prev_body_item, items[i].get()); + page_scroll += get_item_height(items[i].get(), size.x, selected_int_diff_abs < 50, true, merge_with_previous); + if(merge_with_previous) + page_scroll -= spacing_y; + page_scroll += spacing_y; + prev_body_item = items[i].get(); } ++num_items_scrolled; ++i; @@ -545,9 +551,15 @@ namespace QuickMedia { } else if(selected_item_diff < 0) { int num_items_scrolled = 0; int i = prev_selected_item - 1; + BodyItem *prev_body_item = get_previous_visible_item(i); while(num_items_scrolled < selected_int_diff_abs && i >= 0) { if(items[i]->visible) { - page_scroll -= (get_item_height(items[i].get(), size.x, selected_int_diff_abs < 50) + spacing_y); + prev_body_item = get_previous_visible_item(i); + const bool merge_with_previous = body_item_merge_handler && body_item_merge_handler(prev_body_item, items[i].get()); + page_scroll -= get_item_height(items[i].get(), size.x, selected_int_diff_abs < 50, true, merge_with_previous); + if(merge_with_previous) + page_scroll += spacing_y; + page_scroll -= spacing_y; } ++num_items_scrolled; --i; @@ -555,7 +567,9 @@ namespace QuickMedia { prev_selected_item = selected_item; } - selected_item_height = get_item_height(items[selected_item].get(), size.x) + spacing_y; + const bool merge_with_previous = body_item_merge_handler && body_item_merge_handler(get_previous_visible_item(selected_item), items[selected_item].get()); + selected_item_height = get_item_height(items[selected_item].get(), size.x, true, true, merge_with_previous); + selected_item_height += spacing_y; bool selected_item_fits_on_screen = selected_item_height <= size.y; selected_line_top_visible = pos.y - start_y + page_scroll >= 0.0f; selected_line_bottom_visible = pos.y - start_y + page_scroll + selected_item_height <= size.y; @@ -584,11 +598,20 @@ namespace QuickMedia { } if(page_scroll > size.y - selected_item_height && selected_item_fits_on_screen) { - //fprintf(stderr, "top!\n"); + //fprintf(stderr, "top: %f\n", page_scroll - (size.y - selected_item_height)); page_scroll = size.y - selected_item_height; + if(merge_with_previous) + page_scroll += spacing_y*2.0f; + + BodyItem *next_body_item = get_next_visible_item(selected_item); + const bool merge_with_next = next_body_item && body_item_merge_handler && body_item_merge_handler(items[selected_item].get(), next_body_item); + if(merge_with_next) + page_scroll += spacing_y; } else if(page_scroll < 0.0f && selected_line_top_visible && selected_item_fits_on_screen) { //fprintf(stderr, "bottom!\n"); page_scroll = 0.0f; + if(merge_with_previous) + page_scroll += spacing_y; } } @@ -610,9 +633,14 @@ namespace QuickMedia { if(!item->visible) continue; + BodyItem *prev_body_item = get_previous_visible_item(i); + const bool merge_with_previous = body_item_merge_handler && body_item_merge_handler(prev_body_item, item.get()); + item->last_drawn_time = elapsed_time_sec; - float item_height = get_item_height(item.get(), size.x); - prev_pos.y -= (item_height + spacing_y); + float item_height = get_item_height(item.get(), size.x, true, true, merge_with_previous); + float item_height_with_merge = item_height; + item_height_with_merge += spacing_y; + prev_pos.y -= item_height_with_merge; if(prev_pos.y < start_y) { items_cut_off = true; @@ -620,22 +648,28 @@ namespace QuickMedia { first_item_fully_visible = false; } - if(prev_pos.y + item_height + spacing_y <= start_y) + if(prev_pos.y + item_height_with_merge <= start_y) break; // This is needed here rather than above the loop, since update_dirty_text cant be called inside scissor because it corrupts the text for some reason 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); - draw_item(window, item.get(), prev_pos, size, item_height, i, content_progress); + draw_item(window, item.get(), prev_pos, size, item_height, i, content_progress, true, merge_with_previous); glDisable(GL_SCISSOR_TEST); ++num_visible_items; if(first_item_fully_visible) first_fully_visible_item = i; + + if(merge_with_previous) + prev_pos.y += spacing_y; } offset_to_top = prev_pos.y - start_y; + BodyItem *prev_body_item = get_previous_visible_item(selected_item); + float prev_item_height = 0.0f; + sf::Vector2f after_pos = pos; for(int i = selected_item; i < num_items; ++i) { auto &item = items[i]; @@ -644,6 +678,10 @@ namespace QuickMedia { if(!item->visible) continue; + const bool merge_with_previous = body_item_merge_handler && body_item_merge_handler(prev_body_item, item.get()); + if(merge_with_previous) + after_pos.y -= spacing_y; + if(after_pos.y < start_y) { items_cut_off = true; first_item_fully_visible = false; @@ -660,14 +698,15 @@ namespace QuickMedia { } item->last_drawn_time = elapsed_time_sec; - float item_height = get_item_height(item.get(), size.x); + float item_height = get_item_height(item.get(), size.x, true, true, merge_with_previous); // This is needed here rather than above the loop, since update_dirty_text cant be called inside scissor because it corrupts the text for some reason 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); - draw_item(window, item.get(), after_pos, size, item_height, i, content_progress); + draw_item(window, item.get(), after_pos, size, item_height, i, content_progress, true, merge_with_previous); glDisable(GL_SCISSOR_TEST); - after_pos.y += item_height + spacing_y; + after_pos.y += item_height; + after_pos.y += spacing_y; ++num_visible_items; if(after_pos.y - start_y > size.y) { @@ -681,6 +720,9 @@ namespace QuickMedia { if(first_item_fully_visible && first_fully_visible_item == -1) first_fully_visible_item = i; + + prev_body_item = items[i].get(); + prev_item_height = item_height; } if(first_fully_visible_item == -1) @@ -808,6 +850,24 @@ namespace QuickMedia { return content_size; } + // TODO: Cache, and take into consideration updated items and visibility change + BodyItem* Body::get_previous_visible_item(int start_index) { + for(int i = start_index - 1; i >= 0; --i) { + if(items[i]->visible) + return items[i].get(); + } + return nullptr; + } + + // TODO: Cache, and take into consideration updated items and visibility change + BodyItem* Body::get_next_visible_item(int start_index) { + for(int i = start_index + 1; i < (int)items.size(); ++i) { + if(items[i]->visible) + return items[i].get(); + } + return nullptr; + } + static sf::Vector2f to_vec2f(const sf::Vector2i &vec) { return sf::Vector2f(vec.x, vec.y); } @@ -843,7 +903,7 @@ namespace QuickMedia { return ""; } - 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, bool include_embedded_item) { + 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, bool include_embedded_item, bool merge_with_previous) { // TODO: Instead of generating a new hash everytime to access textures, cache the hash of the thumbnail url std::shared_ptr item_thumbnail; if(draw_thumbnails && !item->thumbnail_url.empty()) { @@ -884,7 +944,7 @@ namespace QuickMedia { } float text_offset_x = padding_x; - if(draw_thumbnails && item_thumbnail) { + if(draw_thumbnails && item_thumbnail && !merge_with_previous) { double elapsed_time_thumbnail = 0.0; if(item_thumbnail->loading_state == LoadingState::APPLIED_TO_TEXTURE) elapsed_time_thumbnail = item_thumbnail->texture_applied_time.getElapsedTime().asSeconds(); //thumbnail_fade_duration_sec @@ -943,10 +1003,13 @@ namespace QuickMedia { window.draw(loading_icon); text_offset_x += image_padding_x + content_size.x; } + } else if(merge_with_previous && !item->thumbnail_url.empty()) { + // TODO: Get the previous items text_offset_x instead of + 32.0f * get_ui_scale() + text_offset_x += image_padding_x + (32.0f * get_ui_scale()); } const float timestamp_text_y = std::floor(item_pos.y + padding_y - std::floor(6.0f * get_ui_scale())); - if(item->author_text) { + if(item->author_text && !merge_with_previous) { item->author_text->setPosition(std::floor(item_pos.x + text_offset_x), std::floor(item_pos.y + padding_y - 6.0f * get_ui_scale())); item->author_text->setMaxWidth(size.x - text_offset_x - image_padding_x); item->author_text->draw(window); @@ -1056,10 +1119,10 @@ namespace QuickMedia { } } - float Body::get_item_height(BodyItem *item, float width, bool load_texture, bool include_embedded_item) { + float Body::get_item_height(BodyItem *item, float width, bool load_texture, bool include_embedded_item, bool merge_with_previous) { float image_height = 0.0f; float text_offset_x = padding_x; - if(draw_thumbnails && !item->thumbnail_url.empty()) { + if(draw_thumbnails && !item->thumbnail_url.empty() && !merge_with_previous) { sf::Vector2i content_size = get_item_thumbnail_size(item); image_height = content_size.y; @@ -1100,6 +1163,9 @@ namespace QuickMedia { } else { text_offset_x += image_padding_x + content_size.x; } + } else if(merge_with_previous) { + // TODO: Get the previous items text_offset_x instead of + 32.0f * get_ui_scale() + text_offset_x += image_padding_x + (32.0f * get_ui_scale()); } if(load_texture) @@ -1109,7 +1175,7 @@ namespace QuickMedia { if(item->title_text) { item_height += item->title_text->getHeight() - 2.0f + std::floor(3.0f * get_ui_scale()); } - if(item->author_text) { + if(item->author_text && !merge_with_previous) { item_height += item->author_text->getHeight() - 2.0f + std::floor(3.0f * get_ui_scale()); } if(include_embedded_item && item->embedded_item_status != FetchStatus::NONE) { @@ -1142,7 +1208,8 @@ namespace QuickMedia { } item_height = std::max(item_height, image_height); - return item_height + padding_y * 2.0f; + item_height += (padding_y * 2.0f); + return item_height; } float Body::get_spacing_y() const { diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 7835af2..f020277 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -3706,7 +3706,7 @@ namespace QuickMedia { message->type = MessageType::TEXT; message->timestamp = time(NULL) * 1000; - const sf::Color provisional_message_color(255, 255, 255, 180); + const sf::Color provisional_message_color(255, 255, 255, 150); int num_items = tabs[MESSAGES_TAB_INDEX].body->items.size(); bool scroll_to_end = num_items == 0; @@ -3990,6 +3990,24 @@ namespace QuickMedia { }; }; + tabs[MESSAGES_TAB_INDEX].body->body_item_merge_handler = [](BodyItem *prev_item, BodyItem *this_item) { + Message *message = static_cast(this_item->userdata); + if(!message || !prev_item || !prev_item->userdata) + return false; + + if(is_visual_media_message_type(message->type) && !this_item->thumbnail_url.empty()) + return false; + + Message *prev_message = static_cast(prev_item->userdata); + if(is_visual_media_message_type(prev_message->type) && !prev_item->thumbnail_url.empty()) + return false; + + if(message->user == prev_message->user) + return true; + + return false; + }; + const float tab_spacer_height = 0.0f; sf::Vector2f body_pos; sf::Vector2f body_size; @@ -4884,7 +4902,8 @@ namespace QuickMedia { glScissor(0.0f, 0.0f, this->body_size.x, window_size.y); window.draw(room_list_background); window.draw(room_label); - room_tabs[room_selected_tab].body->draw(window, sf::Vector2f(0.0f, tab_y), sf::Vector2f(this->body_size.x, window_size.y - tab_y), Json::Value::nullSingleton()); + const float padding_x = std::floor(10.0f * get_ui_scale()); + room_tabs[room_selected_tab].body->draw(window, sf::Vector2f(padding_x, tab_y), sf::Vector2f(this->body_size.x - padding_x * 2.0f, window_size.y - tab_y), Json::Value::nullSingleton()); glDisable(GL_SCISSOR_TEST); } -- cgit v1.2.3