From 5dd0248e16522a3672c58a7892d549840257e8dd Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 22 Nov 2020 10:29:58 +0100 Subject: Matrix: add reactions --- src/Body.cpp | 114 +++++++++++++++++++++++++++++++++++++------------ src/QuickMedia.cpp | 63 +++++++++++++++++++++++++-- src/plugins/Matrix.cpp | 37 ++++++++++------ 3 files changed, 170 insertions(+), 44 deletions(-) (limited to 'src') diff --git a/src/Body.cpp b/src/Body.cpp index 430c28f..0b4ec0f 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -17,6 +17,11 @@ static const float padding_y = 5.0f; static const float embedded_item_padding_y = 0.0f; static const double thumbnail_fade_duration_sec = 0.1; +const float reaction_background_padding_x = 7.0f; +const float reaction_background_padding_y = 3.0f; +const float reaction_spacing_x = 5.0f; +static const float reaction_padding_y = 7.0f; + namespace QuickMedia { BodyItem::BodyItem(std::string _title) : visible(true), @@ -75,6 +80,13 @@ namespace QuickMedia { } thumbnail_mask_type = other.thumbnail_mask_type; thumbnail_size = other.thumbnail_size; + reactions.clear(); + for(auto &reaction : other.reactions) { + Reaction reaction_copy; + reaction_copy.text = std::make_unique(*reaction.text); + reaction_copy.userdata = reaction.userdata; + reactions.push_back(std::move(reaction_copy)); + } title = other.title; description = other.description; author = other.author; @@ -549,7 +561,7 @@ namespace QuickMedia { if(body_item->title_text) body_item->title_text->setString(std::move(str)); else - body_item->title_text = std::make_unique(std::move(str), false, 16, width - 50 - image_padding_x * 2.0f); + body_item->title_text = std::make_unique(std::move(str), false, 16, width); body_item->title_text->setFillColor(body_item->get_title_color()); body_item->title_text->updateGeometry(); } @@ -560,7 +572,7 @@ namespace QuickMedia { if(body_item->description_text) body_item->description_text->setString(std::move(str)); else - body_item->description_text = std::make_unique(std::move(str), false, 14, width - 50 - image_padding_x * 2.0f); + body_item->description_text = std::make_unique(std::move(str), false, 14, width); body_item->description_text->setFillColor(body_item->get_description_color()); body_item->description_text->updateGeometry(); } @@ -571,7 +583,7 @@ namespace QuickMedia { if(body_item->author_text) body_item->author_text->setString(std::move(str)); else - body_item->author_text = std::make_unique(std::move(str), true, 14, width - 50 - image_padding_x * 2.0f); + body_item->author_text = std::make_unique(std::move(str), true, 14, width); body_item->author_text->setFillColor(body_item->get_author_color()); body_item->author_text->updateGeometry(); } @@ -706,7 +718,6 @@ namespace QuickMedia { only_show_thumbnail = true; } - bool has_thumbnail_texture = false; // TODO: Verify if this is safe. The thumbnail is being modified in another thread if(item_thumbnail->loading_state == LoadingState::APPLIED_TO_TEXTURE && item_thumbnail->texture.getNativeHandle() != 0) { image.setTexture(item_thumbnail->texture, true); @@ -723,7 +734,6 @@ namespace QuickMedia { else window.draw(image); text_offset_x += image_padding_x + new_image_size.x; - has_thumbnail_texture = true; // 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() && !only_show_thumbnail) { @@ -753,9 +763,7 @@ namespace QuickMedia { loading_icon.setRotation(elapsed_time_sec * 400.0); loading_icon.setColor(sf::Color(255, 255, 255, fallback_fade_alpha)); window.draw(loading_icon); - - if(!has_thumbnail_texture) - text_offset_x += image_padding_x + content_size.x; + text_offset_x += image_padding_x + content_size.x; } } @@ -820,6 +828,31 @@ namespace QuickMedia { item_pos.y += item->description_text->getHeight() - 2.0f; } + if(!item->reactions.empty()) { + sf::RoundedRectangleShape reaction_background(sf::Vector2f(1.0f, 1.0f), 10.0f, 10); + reaction_background.setFillColor(sf::Color(33, 38, 44)); + float reaction_offset_x = 0.0f; + item_pos.y += reaction_padding_y; + float reaction_max_height = 0.0f; + for(auto &reaction : item->reactions) { + reaction.text->setMaxWidth(size.x - text_offset_x - image_padding_x); + reaction.text->updateGeometry(); + reaction_max_height = std::max(reaction_max_height, reaction.text->getHeight()); + if(text_offset_x + reaction_offset_x + reaction.text->getWidth() + reaction_background_padding_x * 2.0f > size.x) { + reaction_offset_x = 0.0f; + item_pos.y += reaction.text->getHeight() + reaction_padding_y + 6.0f; + reaction_max_height = reaction.text->getHeight(); + } + reaction.text->setPosition(std::floor(item_pos.x + text_offset_x + reaction_offset_x + reaction_background_padding_x), std::floor(item_pos.y + padding_y - 4.0f + reaction_background_padding_y)); + reaction_background.setPosition(std::floor(item_pos.x + text_offset_x + reaction_offset_x), std::floor(item_pos.y + padding_y)); + reaction_background.setSize(sf::Vector2f(reaction.text->getWidth() + reaction_background_padding_x * 2.0f, reaction.text->getHeight() + reaction_background_padding_y * 2.0f)); + window.draw(reaction_background); + reaction_offset_x += reaction.text->getWidth() + reaction_background_padding_x * 2.0f + reaction_spacing_x; + reaction.text->draw(window); + } + item_pos.y += reaction_max_height + reaction_padding_y; + } + if(item->timestamp_text) { item->timestamp_text->setPosition(std::floor(item_pos.x + size.x - item->timestamp_text->getLocalBounds().width - padding_x), timestamp_text_y + 8.0f); window.draw(*item->timestamp_text); @@ -844,27 +877,11 @@ namespace QuickMedia { } float Body::get_item_height(BodyItem *item, float width, bool load_texture, bool include_embedded_item) { - if(load_texture) - update_dirty_state(item, width); - float item_height = 0.0f; - if(item->title_text) { - item_height += item->title_text->getHeight() - 2.0f; - } - if(item->author_text) { - item_height += item->author_text->getHeight() - 2.0f; - } - if(include_embedded_item && item->embedded_item_status != FetchStatus::NONE) { - if(item->embedded_item) - item_height += (get_item_height(item->embedded_item.get(), width, load_texture, false) + 4.0f + embedded_item_padding_y * 2.0f); - else - item_height += (embedded_item_load_text.getLocalBounds().height + 4.0f + embedded_item_padding_y * 2.0f); - } - if(item->description_text) { - item_height += item->description_text->getHeight() - 2.0f; - } + float image_height = 0.0f; + float text_offset_x = padding_x; if(draw_thumbnails && !item->thumbnail_url.empty()) { sf::Vector2i content_size = get_item_thumbnail_size(item); - float image_height = content_size.y; + image_height = content_size.y; std::shared_ptr item_thumbnail; auto item_thumbnail_it = item_thumbnail_textures.find(item->thumbnail_url); @@ -899,10 +916,51 @@ namespace QuickMedia { sf::Vector2f image_size_f(image_size.x, image_size.y); auto new_image_size = clamp_to_size(image_size_f, to_vec2f(content_size)); image_height = new_image_size.y; + text_offset_x += image_padding_x + new_image_size.x; + } else { + text_offset_x += image_padding_x + content_size.x; } + } - item_height = std::max(item_height, image_height); + if(load_texture) + update_dirty_state(item, width - text_offset_x - image_padding_x); + + float item_height = 0.0f; + if(item->title_text) { + item_height += item->title_text->getHeight() - 2.0f; + } + if(item->author_text) { + item_height += item->author_text->getHeight() - 2.0f; } + if(include_embedded_item && item->embedded_item_status != FetchStatus::NONE) { + if(item->embedded_item) + item_height += (get_item_height(item->embedded_item.get(), width, load_texture, false) + 4.0f + embedded_item_padding_y * 2.0f); + else + item_height += (embedded_item_load_text.getLocalBounds().height + 4.0f + embedded_item_padding_y * 2.0f); + } + if(item->description_text) { + item_height += item->description_text->getHeight() - 2.0f; + } + + if(!item->reactions.empty()) { + float reaction_offset_x = 0.0f; + item_height += reaction_padding_y; + float reaction_max_height = 0.0f; + for(auto &reaction : item->reactions) { + reaction.text->setMaxWidth(width - text_offset_x - image_padding_x); + reaction.text->updateGeometry(); + reaction_max_height = std::max(reaction_max_height, reaction.text->getHeight()); + if(text_offset_x + reaction_offset_x + reaction.text->getWidth() + reaction_background_padding_x * 2.0f > width) { + reaction_offset_x = 0.0f; + item_height += reaction.text->getHeight() + reaction_padding_y + 6.0f; + reaction_max_height = reaction.text->getHeight(); + } + reaction_offset_x += reaction.text->getWidth() + reaction_background_padding_x * 2.0f + reaction_spacing_x; + } + item_height += reaction_max_height + reaction_padding_y; + } + + item_height = std::max(item_height, image_height); return item_height + padding_y * 2.0f; } diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 1bb7a04..d9b1041 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -3076,7 +3076,7 @@ namespace QuickMedia { body_item->url = message->url; body_item->set_author_color(message->user->display_name_color); body_item->userdata = (void*)message; // Note: message has to be valid as long as body_item is used! - if(message->related_event_type == RelatedEventType::REDACTION || message->related_event_type == RelatedEventType::EDIT) + if(message->related_event_type == RelatedEventType::REDACTION || message->related_event_type == RelatedEventType::EDIT || message->related_event_type == RelatedEventType::REACTION) body_item->visible = false; if(message_contains_user_mention(message->body, my_display_name) || message_contains_user_mention(message->body, my_user_id)) body_item->set_description_color(sf::Color(255, 100, 100)); @@ -3218,8 +3218,8 @@ namespace QuickMedia { body_item->embedded_item = nullptr; body_item->embedded_item_status = FetchStatus::NONE; message->type = MessageType::REDACTION; - message->related_event_id.clear(); - message->related_event_type = RelatedEventType::NONE; + //message->related_event_id.clear(); + //message->related_event_type = RelatedEventType::NONE; body_item->thumbnail_url = current_room->get_user_avatar_url(message->user); body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; body_item->set_description("Message deleted"); @@ -3253,7 +3253,7 @@ namespace QuickMedia { } }; - // TODO: Optimize with hash map? + // TODO: Optimize find_body_item_by_event_id hash map? auto modify_related_messages_in_current_room = [&set_body_as_deleted, &unreferenced_events, &find_body_item_by_event_id, &tabs](Messages &messages) { if(messages.empty()) return; @@ -3279,6 +3279,53 @@ namespace QuickMedia { } }; + std::vector> unresolved_reactions; + // TODO: Optimize find_body_item_by_event_id hash map? + auto process_reactions = [&tabs, &find_body_item_by_event_id, &unresolved_reactions, ¤t_room](Messages &messages) { + if(messages.empty()) + return; + + auto &body_items = tabs[MESSAGES_TAB_INDEX].body->items; + + // TODO: Check in |messages| instead + for(auto it = unresolved_reactions.begin(); it != unresolved_reactions.end();) { + auto body_item = find_body_item_by_event_id(body_items.data(), body_items.size(), (*it)->related_event_id); + if(body_item) { + body_item->add_reaction(current_room->get_user_display_name((*it)->user) + ": " + (*it)->body, (*it).get()); + it = unresolved_reactions.erase(it); + } else { + ++it; + } + } + + for(auto &message : messages) { + if(message->type == MessageType::REACTION) { + auto body_item = find_body_item_by_event_id(body_items.data(), body_items.size(), message->related_event_id); + if(body_item) + body_item->add_reaction(current_room->get_user_display_name(message->user) + ": " + message->body, message.get()); + else + unresolved_reactions.push_back(message); + } else if(message->type == MessageType::REDACTION) { + auto body_item = find_body_item_by_event_id(body_items.data(), body_items.size(), message->related_event_id); + if(body_item && static_cast(body_item->userdata)) { + Message *reaction_message = static_cast(body_item->userdata); + if(reaction_message->type == MessageType::REACTION) { + auto body_item = find_body_item_by_event_id(body_items.data(), body_items.size(), reaction_message->related_event_id); + if(body_item) + body_item->remove_reaction_by_userdata(reaction_message); + } + } else { + for(auto it = unresolved_reactions.begin(); it != unresolved_reactions.end(); ++it) { + if(message->related_event_id == (*it)->event_id) { + unresolved_reactions.erase(it); + break; + } + } + } + } + } + }; + auto pinned_body_items_contains_event = [&tabs](const std::string &event_id) { for(auto &body_item : tabs[PINNED_TAB_INDEX].body->items) { if(static_cast(body_item->userdata)->event_id == event_id) @@ -3345,6 +3392,7 @@ namespace QuickMedia { auto me = matrix->get_me(current_room); tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(messages_to_body_items(current_room, all_messages, current_room->get_user_display_name(me), me->user_id)); modify_related_messages_in_current_room(all_messages); + process_reactions(all_messages); tabs[MESSAGES_TAB_INDEX].body->select_last_item(); std::vector pinned_events; @@ -4059,6 +4107,8 @@ namespace QuickMedia { if((selected_tab == MESSAGES_TAB_INDEX || selected_tab == PINNED_TAB_INDEX) && event.key.code == sf::Keyboard::Enter) { BodyItem *selected = tabs[selected_tab].body->get_selected(); if(selected) { + Message *mess = static_cast(selected->userdata); + fprintf(stderr, "Mess type: %d\n", mess->type); if(!display_url_or_image(selected)) display_url_or_image(selected->embedded_item.get()); } @@ -4268,6 +4318,7 @@ namespace QuickMedia { cleanup_tasks(); tabs.clear(); unreferenced_events.clear(); + unresolved_reactions.clear(); all_messages.clear(); new_page = PageType::CHAT; matrix->stop_sync(); @@ -4388,6 +4439,7 @@ namespace QuickMedia { } add_new_messages_to_current_room(sync_data.messages); modify_related_messages_in_current_room(sync_data.messages); + process_reactions(sync_data.messages); process_pinned_events(sync_data.pinned_events); if(set_read_marker_future.ready()) { @@ -4418,6 +4470,7 @@ namespace QuickMedia { tabs[MESSAGES_TAB_INDEX].body->set_selected_item(selected_item_index); } modify_related_messages_in_current_room(new_messages); + process_reactions(new_messages); // TODO: Do not loop all items, only loop the new items resolve_unreferenced_events_with_body_items(tabs[MESSAGES_TAB_INDEX].body->items.data(), tabs[MESSAGES_TAB_INDEX].body->items.size()); } @@ -4653,6 +4706,8 @@ namespace QuickMedia { filter_sent_messages(all_messages_new); add_new_messages_to_current_room(all_messages_new); modify_related_messages_in_current_room(all_messages_new); + unresolved_reactions.clear(); + process_reactions(all_messages_new); std::vector pinned_events; matrix->get_all_pinned_events(current_room, pinned_events); diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 6dabad9..e5e4922 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -1754,19 +1754,30 @@ namespace QuickMedia { std::string related_event_id; const rapidjson::Value &relates_to_json = GetMember(*content_json, "m.relates_to"); if(relates_to_json.IsObject()) { - const rapidjson::Value &replaces_event_id_json = GetMember(relates_to_json, "event_id"); + const rapidjson::Value &relates_to_event_id_json = GetMember(relates_to_json, "event_id"); const rapidjson::Value &rel_type_json = GetMember(relates_to_json, "rel_type"); - if(replaces_event_id_json.IsString() && rel_type_json.IsString() && strcmp(rel_type_json.GetString(), "m.replace") == 0) { - related_event_id = replaces_event_id_json.GetString(); + const rapidjson::Value &in_reply_to_json = GetMember(relates_to_json, "m.in_reply_to"); + const rapidjson::Value &key_json = GetMember(relates_to_json, "key"); + if(relates_to_event_id_json.IsString() && rel_type_json.IsString() && strcmp(rel_type_json.GetString(), "m.replace") == 0) { + related_event_id = relates_to_event_id_json.GetString(); related_event_type = RelatedEventType::EDIT; - } else { - const rapidjson::Value &in_reply_to_json = GetMember(relates_to_json, "m.in_reply_to"); - if(in_reply_to_json.IsObject()) { - const rapidjson::Value &in_reply_to_event_id = GetMember(in_reply_to_json, "event_id"); - if(in_reply_to_event_id.IsString()) { - related_event_id = in_reply_to_event_id.GetString(); - related_event_type = RelatedEventType::REPLY; - } + } else if(in_reply_to_json.IsObject()) { + const rapidjson::Value &in_reply_to_event_id = GetMember(in_reply_to_json, "event_id"); + if(in_reply_to_event_id.IsString()) { + related_event_id = in_reply_to_event_id.GetString(); + related_event_type = RelatedEventType::REPLY; + } + } else if(relates_to_event_id_json.IsString() && rel_type_json.IsString() && strcmp(rel_type_json.GetString(), "m.annotation") == 0 && key_json.IsString()) { + if(strcmp(type_json.GetString(), "m.reaction") == 0) { + auto message = std::make_shared(); + message->type = MessageType::REACTION; + message->user = user; + message->event_id = event_id_str; + message->body = key_json.GetString(); + message->related_event_id = relates_to_event_id_json.GetString(); + message->related_event_type = RelatedEventType::REACTION; + message->timestamp = timestamp; + return message; } } } @@ -1799,7 +1810,9 @@ namespace QuickMedia { } if(strcmp(type_json.GetString(), "m.room.message") == 0 || strcmp(type_json.GetString(), "m.sticker") == 0) { - + } else if(strcmp(type_json.GetString(), "m.reaction") == 0) { + // An old reaction that has been removed. New reactions are removed with m.redact + return nullptr; } else if(strcmp(type_json.GetString(), "m.room.member") == 0) { std::string user_display_name = room_data->get_user_display_name(user); std::string sender_display_name = room_data->get_user_display_name(user_sender); -- cgit v1.2.3