From 4a7b86829025664f6eeef6fcb5fc0894733f2ad6 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 17 Oct 2020 13:42:31 +0200 Subject: Matrix: make displayname more bright, add circle mask shader for avatar and room thumbnails --- TODO | 3 ++- include/Body.hpp | 8 ++++++++ include/QuickMedia.hpp | 2 ++ include/Text.hpp | 2 +- shaders/circle_mask.glsl | 9 +++++++++ src/AsyncImageLoader.cpp | 1 + src/Body.cpp | 51 ++++++++++++++++++++++++++++-------------------- src/QuickMedia.cpp | 30 ++++++++++++++++++++++------ src/plugins/Matrix.cpp | 9 +++++---- 9 files changed, 82 insertions(+), 33 deletions(-) create mode 100644 shaders/circle_mask.glsl diff --git a/TODO b/TODO index d506e1c..f74ccc6 100644 --- a/TODO +++ b/TODO @@ -108,4 +108,5 @@ Sometimes we fail to get images in mangadex, most common reason being that the m Show redacted messages even when part of the initial sync in matrix. Right now they are hidden while new sync redacted messages are not. Update displayname/avatar in matrix when updated in /sync. Fix inconsistent behavior when editing a message that is replied to in matrix. Right now if the replied to message already exits in the body then its used directly and when editing that message the reply message shows the edit embedded, but not if the edit is of an body item that is created because we dont already have it, -to fix this we could perhaps replace the newly created body items for replies when loading old messages and one of the old messages is also one of the embedded messages (by event id). \ No newline at end of file +to fix this we could perhaps replace the newly created body items for replies when loading old messages and one of the old messages is also one of the embedded messages (by event id). +Add button to skip to next video. MPV has this feature when setting "next" video (can be done over IPC). \ No newline at end of file diff --git a/include/Body.hpp b/include/Body.hpp index cdb0ad0..7032ad8 100644 --- a/include/Body.hpp +++ b/include/Body.hpp @@ -13,6 +13,7 @@ namespace sf { class RenderWindow; + class Shader; } namespace QuickMedia { @@ -25,6 +26,11 @@ namespace QuickMedia { FAILED_TO_LOAD }; + enum class ThumbnailMaskType { + NONE, + CIRCLE + }; + class BodyItem { public: BodyItem(std::string _title); @@ -97,6 +103,7 @@ namespace QuickMedia { sf::Int32 last_drawn_time; EmbeddedItemStatus embedded_item_status = EmbeddedItemStatus::NONE; std::shared_ptr embedded_item; // Used by matrix for example to display reply message body. Note: only the first level of embedded items is rendered (not recursive, this is done on purpose) + ThumbnailMaskType thumbnail_mask_type = ThumbnailMaskType::NONE; private: // TODO: Clean up these strings when set in text, and get_title for example should return |title_text.getString()| std::string title; @@ -188,6 +195,7 @@ namespace QuickMedia { sf::Vector2f thumbnail_fallback_size; sf::Color line_seperator_color; BodyItemRenderCallback body_item_render_callback; + sf::Shader *thumbnail_mask_shader; 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 update_dirty_state(BodyItem *body_item, sf::Vector2f size); diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp index 0a0b509..b7e6814 100644 --- a/include/QuickMedia.hpp +++ b/include/QuickMedia.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -116,6 +117,7 @@ namespace QuickMedia { bool image_download_cancel = false; int exit_code = 0; std::string resources_root; + sf::Shader circle_mask_shader; bool use_tor = false; bool no_video = false; bool use_system_mpv_config = false; diff --git a/include/Text.hpp b/include/Text.hpp index a71bc8e..f24e79d 100644 --- a/include/Text.hpp +++ b/include/Text.hpp @@ -126,7 +126,7 @@ namespace QuickMedia // Takes into consideration if index is the last vertex and the last vertex is a newline, then it should be on its own line int get_vertex_line(int index) const; private: - sf::String str; // TODO: Remove this for non-editable text??? + sf::String str; // TODO: Remove this for non-editable text??? also replace with std::string? then we get more efficient editing of text const sf::Font *font; const sf::Font *cjk_font; unsigned int characterSize; diff --git a/shaders/circle_mask.glsl b/shaders/circle_mask.glsl new file mode 100644 index 0000000..9f82591 --- /dev/null +++ b/shaders/circle_mask.glsl @@ -0,0 +1,9 @@ +uniform sampler2D texture; + +void main() { + vec4 pixel = texture2D(texture, gl_TexCoord[0].xy); + vec2 pixelOffset = gl_TexCoord[0].xy - vec2(0.5, 0.5); + float dist = sqrt(dot(pixelOffset, pixelOffset)); + dist = smoothstep(0.47, 0.5, dist); + gl_FragColor = gl_Color * pixel * vec4(1.0, 1.0, 1.0, 1.0 - dist); +} diff --git a/src/AsyncImageLoader.cpp b/src/AsyncImageLoader.cpp index eaf4564..bfef294 100644 --- a/src/AsyncImageLoader.cpp +++ b/src/AsyncImageLoader.cpp @@ -70,6 +70,7 @@ namespace QuickMedia { return ""; } + // TODO: Run in 5 different threads, and add download_to_file(_cache) to reduce memory usage in each (use -O curl option) bool AsyncImageLoader::load_thumbnail(const std::string &url, bool local, sf::Vector2i resize_target_size, bool use_tor, std::shared_ptr thumbnail_data) { update(); diff --git a/src/Body.cpp b/src/Body.cpp index f6800b8..11692aa 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -2,6 +2,7 @@ #include "../include/QuickMedia.hpp" #include "../include/Scale.hpp" #include "../plugins/Plugin.hpp" +#include #include #include #include @@ -43,6 +44,8 @@ namespace QuickMedia { draw_thumbnails(false), wrap_around(false), line_seperator_color(sf::Color(32, 37, 43, 255)), + body_item_render_callback(nullptr), + thumbnail_mask_shader(nullptr), program(program), selected_item(0), prev_selected_item(0), @@ -179,18 +182,12 @@ namespace QuickMedia { page_scroll = 0.0f; } - // TODO: Optimize with memcpy and changing capacity before loop void Body::prepend_items(BodyItems new_items) { - for(auto &body_item : new_items) { - items.insert(items.begin(), std::move(body_item)); - } + items.insert(items.begin(), std::make_move_iterator(new_items.begin()), std::make_move_iterator(new_items.end())); } - // TODO: Optimize with memcpy and changing capacity before loop void Body::append_items(BodyItems new_items) { - for(auto &body_item : new_items) { - items.push_back(std::move(body_item)); - } + items.insert(items.end(), std::make_move_iterator(new_items.begin()), std::make_move_iterator(new_items.end())); } void Body::insert_item_by_timestamp(std::shared_ptr body_item) { @@ -383,19 +380,13 @@ namespace QuickMedia { if(!item->visible) continue; - update_dirty_state(item.get(), size); - item->last_drawn_time = elapsed_time; - - float item_height = get_item_height(item.get()); - - if((after_pos.y - start_y) + item_height + spacing_y > size.y) - last_item_fully_visible = false; - else - last_fully_visible_item = i; - if(after_pos.y - start_y >= size.y) break; + float item_height = get_item_height(item.get()); + update_dirty_state(item.get(), size); + item->last_drawn_time = elapsed_time; + // 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); @@ -403,6 +394,11 @@ namespace QuickMedia { glDisable(GL_SCISSOR_TEST); after_pos.y += item_height + spacing_y; ++num_visible_items; + + if(after_pos.y - start_y > size.y) + last_item_fully_visible = false; + else + last_fully_visible_item = i; } if(last_fully_visible_item == -1) @@ -576,13 +572,26 @@ namespace QuickMedia { 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); + if(thumbnail_mask_shader && item->thumbnail_mask_type == ThumbnailMaskType::CIRCLE) + window.draw(image, thumbnail_mask_shader); + else + 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); + if(thumbnail_mask_shader && item->thumbnail_mask_type == ThumbnailMaskType::CIRCLE) { + // TODO: Use the mask shader instead, but a vertex shader is also needed for that to pass the vertex coordinates since + // shapes dont have texture coordinates. + // TODO: Cache circle shape + sf::CircleShape circle_shape(image_fallback.getSize().x * 0.5f); + circle_shape.setFillColor(sf::Color::White); + circle_shape.setPosition(item_pos + sf::Vector2f(image_padding_x, padding_y)); + window.draw(circle_shape); + } else { + 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; } } diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index bd4b2d4..a2c936f 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -349,6 +349,11 @@ namespace QuickMedia { abort(); } + if(!circle_mask_shader.loadFromFile(resources_root + "shaders/circle_mask.glsl", sf::Shader::Type::Fragment)) { + fprintf(stderr, "Failed to load %s/shaders/circle_mask.glsl", resources_root.c_str()); + abort(); + } + struct sigaction action; action.sa_handler = sigpipe_handler; sigemptyset(&action.sa_mask); @@ -2896,8 +2901,10 @@ namespace QuickMedia { body_item->thumbnail_url = message->thumbnail_url; else if(!message->url.empty() && message->type == MessageType::IMAGE) body_item->thumbnail_url = message->url; - else + else { body_item->thumbnail_url = message->user->avatar_url; + body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; + } // TODO: Show image thumbnail inline instead of url to image and showing it as the thumbnail of the body item body_item->url = message->url; body_item->author_color = message->user->display_name_color; @@ -2931,6 +2938,7 @@ namespace QuickMedia { messages_tab.body->thumbnail_resize_target_size.y = 337; messages_tab.body->thumbnail_fallback_size.x = 32; messages_tab.body->thumbnail_fallback_size.y = 32; + messages_tab.body->thumbnail_mask_shader = &circle_mask_shader; //messages_tab.body->line_seperator_color = sf::Color::Transparent; messages_tab.text = sf::Text("Messages", *font, tab_text_size); tabs.push_back(std::move(messages_tab)); @@ -2942,6 +2950,7 @@ namespace QuickMedia { //rooms_tab.body->line_seperator_color = sf::Color::Transparent; rooms_tab.body->thumbnail_fallback_size.x = 32; rooms_tab.body->thumbnail_fallback_size.y = 32; + rooms_tab.body->thumbnail_mask_shader = &circle_mask_shader; rooms_tab.text = sf::Text("Rooms", *font, tab_text_size); tabs.push_back(std::move(rooms_tab)); @@ -3182,7 +3191,7 @@ namespace QuickMedia { const float tab_vertical_offset = 10.0f; - sf::Text room_name_text("", *font, 18); + sf::Text room_name_text("", *bold_font, 18); const float room_name_text_height = 20.0f; const float room_name_text_padding_y = 10.0f; const float room_name_total_height = room_name_text_height + room_name_text_padding_y * 2.0f; @@ -3308,6 +3317,7 @@ namespace QuickMedia { auto body_item = BodyItem::create(std::move(room_name)); body_item->thumbnail_url = room->avatar_url; body_item->userdata = room.get(); // Note: this has to be valid as long as the room list is valid! + body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; tabs[ROOMS_TAB_INDEX].body->items.push_back(body_item); body_items_by_room[room] = { body_item, true, 0 }; } @@ -3344,8 +3354,12 @@ namespace QuickMedia { auto body_item = find_body_item_by_event_id(body_items, num_body_items, message->related_event_id); if(body_item) { body_item->set_description(message->body); - if(message->related_event_type == RelatedEventType::REDACTION) + if(message->related_event_type == RelatedEventType::REDACTION) { + body_item->embedded_item = nullptr; + body_item->embedded_item_status = EmbeddedItemStatus::NONE; body_item->thumbnail_url = message->user->avatar_url; + body_item->thumbnail_mask_type = ThumbnailMaskType::NONE; + } it = unreferenced_events.erase(it); } else { ++it; @@ -3366,8 +3380,12 @@ namespace QuickMedia { auto body_item = find_body_item_by_event_id(body_items.data(), body_items.size(), message->related_event_id); if(body_item) { body_item->set_description(message->body); - if(message->related_event_type == RelatedEventType::REDACTION) + if(message->related_event_type == RelatedEventType::REDACTION) { + body_item->embedded_item = nullptr; + body_item->embedded_item_status = EmbeddedItemStatus::NONE; body_item->thumbnail_url = message->user->avatar_url; + body_item->thumbnail_mask_type = ThumbnailMaskType::NONE; + } } else { unreferenced_events.push_back(message); } @@ -3883,10 +3901,10 @@ namespace QuickMedia { room_avatar_texture_size.x *= room_avatar_sprite.getScale().x; room_avatar_texture_size.y *= room_avatar_sprite.getScale().y; room_avatar_sprite.setPosition(body_pos.x, room_name_total_height * 0.5f - room_avatar_texture_size.y * 0.5f + 5.0f); - window.draw(room_avatar_sprite); + window.draw(room_avatar_sprite, &circle_mask_shader); room_name_text_offset_x += room_avatar_texture_size.x + 10.0f; } - room_name_text.setPosition(body_pos.x + room_name_text_offset_x, room_name_text_padding_y); + room_name_text.setPosition(body_pos.x + room_name_text_offset_x, room_name_text_padding_y + 4.0f); window.draw(room_name_text); } diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 1cb2aa5..ea5f86c 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -269,10 +269,11 @@ namespace QuickMedia { for(unsigned char c : user_id) { color = (color * 16777619) ^ c; } - sf::Color result = (sf::Color)color; - result.r = 64 + std::max(0, (int)result.r - 64); - result.g = 64 + std::max(0, (int)result.g - 64); - result.b = 64 + std::max(0, (int)result.b - 64); + sf::Uint8 *col = (sf::Uint8*)&color; + sf::Color result(col[0], col[1], col[2]); + result.r = std::min(255, 80 + (int)result.r); + result.g = std::min(255, 80 + (int)result.g); + result.b = std::min(255, 80 + (int)result.b); result.a = 255; return result; } -- cgit v1.2.3