aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2020-10-19 23:33:23 +0200
committerdec05eba <dec05eba@protonmail.com>2020-10-20 02:06:49 +0200
commit6a2b5008be8104680826fe40fa8e674e9357c044 (patch)
treec7d88abee4e71ebbba68384c1686829656933b1c
parent41fe990530c546b4cac7e000b40481f87fb33305 (diff)
Add thumbnail loading animation
Use correct ref in matrix replies, make text that contains our user id also count as a mention.
-rw-r--r--images/loading_icon.pngbin0 -> 2766 bytes
-rw-r--r--include/Body.hpp8
-rw-r--r--include/QuickMedia.hpp1
-rw-r--r--src/Body.cpp27
-rw-r--r--src/QuickMedia.cpp26
-rw-r--r--src/plugins/Matrix.cpp16
6 files changed, 52 insertions, 26 deletions
diff --git a/images/loading_icon.png b/images/loading_icon.png
new file mode 100644
index 0000000..6c11fbc
--- /dev/null
+++ b/images/loading_icon.png
Binary files differ
diff --git a/include/Body.hpp b/include/Body.hpp
index 9c7c1c1..ce61811 100644
--- a/include/Body.hpp
+++ b/include/Body.hpp
@@ -3,7 +3,6 @@
#include "Text.hpp"
#include "AsyncImageLoader.hpp"
#include <SFML/Graphics/Text.hpp>
-#include <SFML/Graphics/Texture.hpp>
#include <SFML/Graphics/RectangleShape.hpp>
#include <SFML/Graphics/Sprite.hpp>
#include "../external/RoundedRectangleShape.hpp"
@@ -14,6 +13,7 @@
namespace sf {
class RenderWindow;
class Shader;
+ class Texture;
}
namespace QuickMedia {
@@ -113,7 +113,7 @@ namespace QuickMedia {
std::vector<size_t> replies;
std::string post_number;
void *userdata; // Not managed, should be deallocated by whoever sets this
- sf::Int32 last_drawn_time;
+ double last_drawn_time;
EmbeddedItemStatus embedded_item_status = EmbeddedItemStatus::NONE;
std::shared_ptr<BodyItem> 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;
@@ -134,7 +134,7 @@ namespace QuickMedia {
class Body {
public:
- Body(Program *program, sf::Font *font, sf::Font *bold_font, sf::Font *cjk_font);
+ Body(Program *program, sf::Font *font, sf::Font *bold_font, sf::Font *cjk_font, sf::Texture &loading_icon_texture);
// Select previous page, ignoring invisible items. Returns true if the item was changed. This can be used to check if the top was hit when wrap_around is set to false
bool select_previous_page();
@@ -229,9 +229,11 @@ namespace QuickMedia {
sf::RectangleShape item_separator;
sf::RoundedRectangleShape item_background;
sf::Sprite image;
+ sf::Sprite loading_icon;
int num_visible_items;
bool last_item_fully_visible;
int last_fully_visible_item;
sf::Clock draw_timer;
+ double elapsed_time_sec = 0.0;
};
} \ No newline at end of file
diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp
index 955fc9c..45c499a 100644
--- a/include/QuickMedia.hpp
+++ b/include/QuickMedia.hpp
@@ -97,6 +97,7 @@ namespace QuickMedia {
std::unique_ptr<sf::Font> cjk_font;
const char *plugin_name = nullptr;
sf::Texture plugin_logo;
+ sf::Texture loading_icon;
PageType current_page;
std::stack<PageType> page_stack;
int image_index;
diff --git a/src/Body.cpp b/src/Body.cpp
index 0294d97..1d06559 100644
--- a/src/Body.cpp
+++ b/src/Body.cpp
@@ -25,7 +25,7 @@ namespace QuickMedia {
dirty_timestamp(false),
thumbnail_is_local(false),
userdata(nullptr),
- last_drawn_time(0),
+ last_drawn_time(0.0),
timestamp(0),
title_color(sf::Color::White),
author_color(sf::Color::White),
@@ -35,7 +35,7 @@ namespace QuickMedia {
set_title(std::move(_title));
}
- Body::Body(Program *program, sf::Font *font, sf::Font *bold_font, sf::Font *cjk_font) :
+ Body::Body(Program *program, sf::Font *font, sf::Font *bold_font, sf::Font *cjk_font, sf::Texture &loading_icon_texture) :
font(font),
bold_font(bold_font),
cjk_font(cjk_font),
@@ -52,6 +52,7 @@ namespace QuickMedia {
prev_selected_item(0),
page_scroll(0.0f),
item_background(sf::Vector2f(1.0f, 1.0f), 10.0f, 10),
+ loading_icon(loading_icon_texture),
num_visible_items(0),
last_item_fully_visible(true),
last_fully_visible_item(-1)
@@ -61,6 +62,8 @@ namespace QuickMedia {
thumbnail_resize_target_size.x = 120;
thumbnail_resize_target_size.y = 120;
item_background.setFillColor(sf::Color(55, 60, 68));
+ sf::Vector2f loading_icon_size(loading_icon.getTexture()->getSize().x, loading_icon.getTexture()->getSize().y);
+ loading_icon.setOrigin(loading_icon_size.x * 0.5f, loading_icon_size.y * 0.5f);
}
// TODO: Make this work with wraparound enabled?
@@ -274,7 +277,7 @@ namespace QuickMedia {
sf::Vector2f scissor_size = size;
const float start_y = pos.y;
- const sf::Int32 elapsed_time = draw_timer.getElapsedTime().asMilliseconds();
+ elapsed_time_sec = draw_timer.getElapsedTime().asSeconds();
//item_background.setFillColor(front_color);
//item_background.setOutlineThickness(1.0f);
@@ -354,7 +357,7 @@ namespace QuickMedia {
continue;
update_dirty_state(item.get(), size);
- item->last_drawn_time = elapsed_time;
+ item->last_drawn_time = elapsed_time_sec;
float item_height = get_item_height(item.get());
prev_pos.y -= (item_height + spacing_y);
@@ -383,7 +386,7 @@ namespace QuickMedia {
update_dirty_state(item.get(), size);
float item_height = get_item_height(item.get());
- item->last_drawn_time = elapsed_time;
+ item->last_drawn_time = elapsed_time_sec;
// 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);
@@ -410,10 +413,9 @@ namespace QuickMedia {
}
// TODO: Only do this for items that are not visible, do not loop all items.
- // TODO: Verify if this only runs for items that are not visible, and only once
// TODO: Improve performance! right now it can use up to 5-7% cpu with a lot of items!
for(auto &body_item : items) {
- if(elapsed_time - body_item->last_drawn_time >= 1500) {
+ if(elapsed_time_sec - body_item->last_drawn_time >= 1.5) {
clear_body_item_cache(body_item.get());
}
}
@@ -600,7 +602,8 @@ namespace QuickMedia {
if(item->thumbnail_size.x > 0 && item->thumbnail_size.y > 0)
content_size = sf::Vector2f(item->thumbnail_size.x, item->thumbnail_size.y);
- sf::Color fallback_color(52, 58, 70, (1.0 - thumbnail_fade_progress) * 255);
+ sf::Uint8 fallback_fade_alpha = (1.0 - thumbnail_fade_progress) * 255;
+ sf::Color fallback_color(52, 58, 70, fallback_fade_alpha);
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.
@@ -616,6 +619,14 @@ namespace QuickMedia {
window.draw(image_fallback);
}
+ sf::Vector2f loading_icon_size(loading_icon.getTexture()->getSize().x, loading_icon.getTexture()->getSize().y);
+ auto new_loading_icon_size = clamp_to_size(loading_icon_size, content_size);
+ loading_icon.setPosition(item_pos + sf::Vector2f(image_padding_x, padding_y) + (content_size * 0.5f));
+ loading_icon.setScale(get_ratio(loading_icon_size, new_loading_icon_size));
+ 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;
}
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index a32febb..c47147f 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -355,6 +355,12 @@ namespace QuickMedia {
abort();
}
+ if(!loading_icon.loadFromFile(resources_root + "images/loading_icon.png")) {
+ fprintf(stderr, "Failed to load %s/images/loading_icon.png", resources_root.c_str());
+ abort();
+ }
+ loading_icon.setSmooth(true);
+
struct sigaction action;
action.sa_handler = sigpipe_handler;
sigemptyset(&action.sa_mask);
@@ -883,7 +889,7 @@ namespace QuickMedia {
}
std::unique_ptr<Body> Program::create_body() {
- return std::make_unique<Body>(this, font.get(), bold_font.get(), cjk_font.get());
+ return std::make_unique<Body>(this, font.get(), bold_font.get(), cjk_font.get(), loading_icon);
}
std::unique_ptr<SearchBar> Program::create_search_bar(const std::string &placeholder, int search_delay) {
@@ -2915,7 +2921,7 @@ namespace QuickMedia {
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)
body_item->visible = false;
- if(message->mentions_me || (me && message_contains_user_mention(message->body, me->display_name)))
+ if(message->mentions_me || (me && (message_contains_user_mention(message->body, me->display_name) || message_contains_user_mention(message->body, me->user_id))))
body_item->set_description_color(sf::Color(255, 100, 100));
return body_item;
}
@@ -2938,7 +2944,7 @@ namespace QuickMedia {
ChatTab messages_tab;
messages_tab.type = ChatTabType::MESSAGES;
- messages_tab.body = std::make_unique<Body>(this, font.get(), bold_font.get(), cjk_font.get());
+ messages_tab.body = std::make_unique<Body>(this, font.get(), bold_font.get(), cjk_font.get(), loading_icon);
messages_tab.body->draw_thumbnails = true;
messages_tab.body->thumbnail_resize_target_size = CHAT_MESSAGE_THUMBNAIL_MAX_SIZE;
messages_tab.body->thumbnail_mask_shader = &circle_mask_shader;
@@ -2948,7 +2954,7 @@ namespace QuickMedia {
ChatTab rooms_tab;
rooms_tab.type = ChatTabType::ROOMS;
- rooms_tab.body = std::make_unique<Body>(this, font.get(), bold_font.get(), cjk_font.get());
+ rooms_tab.body = std::make_unique<Body>(this, font.get(), bold_font.get(), cjk_font.get(), loading_icon);
rooms_tab.body->draw_thumbnails = true;
//rooms_tab.body->line_separator_color = sf::Color::Transparent;
rooms_tab.body->thumbnail_mask_shader = &circle_mask_shader;
@@ -3001,11 +3007,11 @@ namespace QuickMedia {
// Swap room with the top one
// TODO: Optimize with hash map instead of linear search? or cache the index
- int room_body_index = tabs[ROOMS_TAB_INDEX].body->get_index_by_body_item(room_body_item);
- if(room_body_index != -1) {
- std::swap(tabs[ROOMS_TAB_INDEX].body->items[room_body_index], tabs[ROOMS_TAB_INDEX].body->items[room_swap_index]);
- ++room_swap_index;
- }
+ // int room_body_index = tabs[ROOMS_TAB_INDEX].body->get_index_by_body_item(room_body_item);
+ // if(room_body_index != -1) {
+ // std::swap(tabs[ROOMS_TAB_INDEX].body->items[room_body_index], tabs[ROOMS_TAB_INDEX].body->items[room_swap_index]);
+ // ++room_swap_index;
+ // }
} else if(is_first_sync) {
room_body_item->set_description(matrix->message_get_author_displayname(messages.back().get()) + ": " + extract_first_line(messages.back()->body, 150));
room->has_unread_mention = false;
@@ -3313,7 +3319,7 @@ namespace QuickMedia {
const float chat_input_padding_x = 15.0f;
const float chat_input_padding_y = 15.0f;
- Body url_selection_body(this, font.get(), bold_font.get(), cjk_font.get());
+ Body url_selection_body(this, font.get(), bold_font.get(), cjk_font.get(), loading_icon);
auto launch_url = [this, &video_page, &redraw](const std::string &url) mutable {
if(url.empty())
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index 35f5302..ff0d498 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -462,7 +462,7 @@ namespace QuickMedia {
// TODO: Is @room ok? shouldn't we also check if the user has permission to do @room? (only when notifications are limited to @mentions)
if(has_unread_notifications && me)
- new_message->mentions_me = message_contains_user_mention(new_message->body, me->display_name) || message_contains_user_mention(new_message->body, "@room");
+ new_message->mentions_me = message_contains_user_mention(new_message->body, me->display_name) || message_contains_user_mention(new_message->body, me->user_id) || message_contains_user_mention(new_message->body, "@room");
new_messages.push_back(std::move(new_message));
}
@@ -978,17 +978,23 @@ namespace QuickMedia {
static std::string create_body_for_message_reply(const Message *message, const std::string &body) {
return "> <" + message->user->user_id + "> " + block_quote(get_reply_message(message)) + "\n\n" + body;
}
+
+ static std::string extract_homeserver_from_room_id(const std::string &room_id) {
+ size_t sep_index = room_id.find(':');
+ if(sep_index != std::string::npos)
+ return room_id.substr(sep_index + 1);
+ return "";
+ }
- static std::string create_formatted_body_for_message_reply(const Message *message, const std::string &body) {
+ static std::string create_formatted_body_for_message_reply(RoomData *room, const Message *message, const std::string &body) {
std::string formatted_body = body;
std::string related_to_body = get_reply_message(message);
html_escape_sequences(formatted_body);
html_escape_sequences(related_to_body);
- // TODO: Fix invalid.url, etc to use same as element. This is required to navigate to reply message in element mobile.
// TODO: Add keybind to navigate to the reply message, which would also depend on this formatting.
return "<mx-reply>"
"<blockquote>"
- "<a href=\"https://invalid.url\">In reply to</a>"
+ "<a href=\"https://matrix.to/#/" + room->id + "/" + message->event_id + "?via=" + extract_homeserver_from_room_id(room->id) + "\">In reply to</a>"
"<a href=\"https://matrix.to/#/" + message->user->user_id + "\">" + message->user->user_id + "</a><br>" + std::move(related_to_body) +
"</blockquote>"
"</mx-reply>" + std::move(formatted_body);
@@ -1018,7 +1024,7 @@ namespace QuickMedia {
relates_to_json.AddMember("m.in_reply_to", std::move(in_reply_to_json), relates_to_json.GetAllocator());
std::string message_reply_body = create_body_for_message_reply(relates_to_message_raw, body); // Yes, the reply is to the edited message but the event_id reference is to the original message...
- std::string formatted_message_reply_body = create_formatted_body_for_message_reply(relates_to_message_raw, body);
+ std::string formatted_message_reply_body = create_formatted_body_for_message_reply(room, relates_to_message_raw, body);
rapidjson::Document request_data(rapidjson::kObjectType);
request_data.AddMember("msgtype", "m.text", request_data.GetAllocator()); // TODO: Allow image reply? element doesn't do that but we could!