From 30dbaeb2b175c1e67f57aba748ced1a2280fb56d Mon Sep 17 00:00:00 2001 From: dec05eba Date: Thu, 1 Oct 2020 19:12:33 +0200 Subject: Matrix: add room name and avatar of the current room at the top --- src/AsyncImageLoader.cpp | 138 +++++++++++++++++++++++++++++++++++++++++++++++ src/Body.cpp | 122 +---------------------------------------- src/QuickMedia.cpp | 70 +++++++++++++++++++++--- src/Storage.cpp | 4 ++ src/plugins/Matrix.cpp | 1 + 5 files changed, 207 insertions(+), 128 deletions(-) create mode 100644 src/AsyncImageLoader.cpp (limited to 'src') diff --git a/src/AsyncImageLoader.cpp b/src/AsyncImageLoader.cpp new file mode 100644 index 0000000..020baf1 --- /dev/null +++ b/src/AsyncImageLoader.cpp @@ -0,0 +1,138 @@ +#include "../include/AsyncImageLoader.hpp" +#include "../include/base64_url.hpp" +#include "../include/Storage.hpp" +#include "../include/DownloadUtils.hpp" +#include "../include/ImageUtils.hpp" +#include "../include/Scale.hpp" +#include + +namespace QuickMedia { + static sf::Vector2f to_vec2f(const sf::Vector2u &vec) { + return sf::Vector2f(vec.x, vec.y); + } + + static sf::Vector2f to_vec2f(const sf::Vector2i &vec) { + return sf::Vector2f(vec.x, vec.y); + } + + static sf::Vector2u to_vec2u(const sf::Vector2f &vec) { + return sf::Vector2u(vec.x, vec.y); + } + + static void copy_resize(const sf::Image &source, sf::Image &destination, sf::Vector2u destination_size) { + const sf::Vector2u source_size = source.getSize(); + if(source_size.x == 0 || source_size.y == 0 || destination_size.x == 0 || destination_size.y == 0) + return; + + //float width_ratio = (float)source_size.x / (float)destination_size.x; + //float height_ratio = (float)source_size.y / (float)destination_size.y; + + const sf::Uint8 *source_pixels = source.getPixelsPtr(); + // TODO: Remove this somehow. Right now we need to allocate this and also allocate the same array in the destination image + sf::Uint32 *destination_pixels = new sf::Uint32[destination_size.x * destination_size.y]; + sf::Uint32 *destination_pixel = destination_pixels; + for(unsigned int y = 0; y < destination_size.y; ++y) { + for(unsigned int x = 0; x < destination_size.x; ++x) { + int scaled_x = ((float)x / (float)destination_size.x) * source_size.x; + int scaled_y = ((float)y / (float)destination_size.y) * source_size.y; + //float scaled_x = x * width_ratio; + //float scaled_y = y * height_ratio; + + //sf::Uint32 *source_pixel = (sf::Uint32*)(source_pixels + (int)(scaled_x + scaled_y * source_size.x) * 4); + sf::Uint32 *source_pixel = (sf::Uint32*)(source_pixels + (scaled_x + scaled_y * source_size.x) * 4); + *destination_pixel = *source_pixel; + ++destination_pixel; + } + } + destination.create(destination_size.x, destination_size.y, (sf::Uint8*)destination_pixels); + delete []destination_pixels; + } + + static bool save_image_as_thumbnail_atomic(const sf::Image &image, const Path &thumbnail_path, const char *ext) { + Path tmp_path = thumbnail_path; + tmp_path.append(".tmp"); + const char *thumbnail_path_ext = thumbnail_path.ext(); + if(is_image_ext(ext)) + tmp_path.append(ext); + else if(is_image_ext(thumbnail_path_ext)) + tmp_path.append(thumbnail_path_ext); + else + tmp_path.append(".png"); + return image.saveToFile(tmp_path.data) && (rename(tmp_path.data.c_str(), thumbnail_path.data.c_str()) == 0); + } + + // Returns empty string if no extension + static const char* get_ext(const std::string &path) { + size_t index = path.rfind('.'); + if(index == std::string::npos) + return ""; + return path.c_str() + index; + } + + bool AsyncImageLoader::load_thumbnail(const std::string &url, bool local, sf::Vector2i resize_target_size, bool use_tor, std::shared_ptr thumbnail_data) { + update(); + + if(loading_image) + return false; + + loading_image = true; + + assert(thumbnail_data->loading_state == LoadingState::NOT_LOADED); + thumbnail_data->loading_state = LoadingState::LOADING; + + if(url.empty()) { + thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; + loading_image = false; + return true; + } + + load_image_future = std::async(std::launch::async, [url, local, resize_target_size, thumbnail_data, use_tor]() mutable { + // TODO: Use sha256 instead of base64_url encoding + Path thumbnail_path = get_cache_dir().join("thumbnails").join(base64_url::encode(url)); + + thumbnail_data->image = std::make_unique(); + if(thumbnail_data->image->loadFromFile(thumbnail_path.data)) { + fprintf(stderr, "Loaded %s from thumbnail cache\n", url.c_str()); + thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; + return; + } else { + if(local) { + if(!thumbnail_data->image->loadFromFile(url)) { + thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; + return; + } + } else { + std::string texture_data; + if(download_to_string_cache(url, texture_data, {}, use_tor, true) != DownloadResult::OK || !thumbnail_data->image->loadFromMemory(texture_data.data(), texture_data.size())) { + thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; + return; + } + } + } + + if(resize_target_size.x != 0 && resize_target_size.y != 0) { + sf::Vector2u new_image_size = to_vec2u(clamp_to_size(to_vec2f(thumbnail_data->image->getSize()), to_vec2f(resize_target_size))); + if(new_image_size.x < thumbnail_data->image->getSize().x || new_image_size.y < thumbnail_data->image->getSize().y) { + auto destination_image = std::make_unique(); + copy_resize(*thumbnail_data->image, *destination_image, new_image_size); + thumbnail_data->image = std::move(destination_image); + save_image_as_thumbnail_atomic(*thumbnail_data->image, thumbnail_path, get_ext(url)); + thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; + return; + } + } + + thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; + return; + }); + + return true; + } + + void AsyncImageLoader::update() { + if(loading_image && load_image_future.valid() && load_image_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + load_image_future.get(); + loading_image = false; + } + } +} \ No newline at end of file diff --git a/src/Body.cpp b/src/Body.cpp index 13e3d7d..843a1b1 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -1,8 +1,6 @@ #include "../include/Body.hpp" #include "../include/QuickMedia.hpp" #include "../include/Scale.hpp" -#include "../include/base64_url.hpp" -#include "../include/ImageUtils.hpp" #include "../plugins/Plugin.hpp" #include #include @@ -72,7 +70,6 @@ namespace QuickMedia { wrap_around(false), line_seperator_color(sf::Color(32, 37, 43, 255)), program(program), - loading_thumbnail(false), selected_item(0), prev_selected_item(0), page_scroll(0.0f), @@ -271,115 +268,6 @@ namespace QuickMedia { //page_scroll = 0.0f; } - static sf::Vector2f to_vec2f(const sf::Vector2u &vec) { - return sf::Vector2f(vec.x, vec.y); - } - - static sf::Vector2f to_vec2f(const sf::Vector2i &vec) { - return sf::Vector2f(vec.x, vec.y); - } - - static sf::Vector2u to_vec2u(const sf::Vector2f &vec) { - return sf::Vector2u(vec.x, vec.y); - } - - static void copy_resize(const sf::Image &source, sf::Image &destination, sf::Vector2u destination_size) { - const sf::Vector2u source_size = source.getSize(); - if(source_size.x == 0 || source_size.y == 0 || destination_size.x == 0 || destination_size.y == 0) - return; - - //float width_ratio = (float)source_size.x / (float)destination_size.x; - //float height_ratio = (float)source_size.y / (float)destination_size.y; - - const sf::Uint8 *source_pixels = source.getPixelsPtr(); - // TODO: Remove this somehow. Right now we need to allocate this and also allocate the same array in the destination image - sf::Uint32 *destination_pixels = new sf::Uint32[destination_size.x * destination_size.y]; - sf::Uint32 *destination_pixel = destination_pixels; - for(unsigned int y = 0; y < destination_size.y; ++y) { - for(unsigned int x = 0; x < destination_size.x; ++x) { - int scaled_x = ((float)x / (float)destination_size.x) * source_size.x; - int scaled_y = ((float)y / (float)destination_size.y) * source_size.y; - //float scaled_x = x * width_ratio; - //float scaled_y = y * height_ratio; - - //sf::Uint32 *source_pixel = (sf::Uint32*)(source_pixels + (int)(scaled_x + scaled_y * source_size.x) * 4); - sf::Uint32 *source_pixel = (sf::Uint32*)(source_pixels + (scaled_x + scaled_y * source_size.x) * 4); - *destination_pixel = *source_pixel; - ++destination_pixel; - } - } - destination.create(destination_size.x, destination_size.y, (sf::Uint8*)destination_pixels); - delete []destination_pixels; - } - - static bool save_image_as_thumbnail_atomic(const sf::Image &image, const Path &thumbnail_path, const char *ext) { - Path tmp_path = thumbnail_path; - tmp_path.append(".tmp"); - const char *thumbnail_path_ext = thumbnail_path.ext(); - if(is_image_ext(ext)) - tmp_path.append(ext); - else if(is_image_ext(thumbnail_path_ext)) - tmp_path.append(thumbnail_path_ext); - else - tmp_path.append(".png"); - return image.saveToFile(tmp_path.data) && (rename(tmp_path.data.c_str(), thumbnail_path.data.c_str()) == 0); - } - - // Returns empty string if no extension - static const char* get_ext(const std::string &path) { - size_t index = path.rfind('.'); - if(index == std::string::npos) - return ""; - return path.c_str() + index; - } - - // TODO: Do not load thumbnails for images larger than 30mb. - // TODO: Load the thumbnail embedded in the file instead. - void Body::load_thumbnail_from_url(const std::string &url, bool local, sf::Vector2i thumbnail_resize_target_size, std::shared_ptr thumbnail_data) { - assert(!loading_thumbnail); - loading_thumbnail = true; - - load_thumbnail_future = std::async(std::launch::async, [this, url, local, thumbnail_resize_target_size, thumbnail_data]() mutable { - // TODO: Use sha256 instead of base64_url encoding - Path thumbnail_path = get_cache_dir().join("thumbnails").join(base64_url::encode(url)); - - thumbnail_data->image = std::make_unique(); - if(thumbnail_data->image->loadFromFile(thumbnail_path.data)) { - fprintf(stderr, "Loaded %s from thumbnail cache\n", url.c_str()); - thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; - return; - } else { - if(local) { - if(!thumbnail_data->image->loadFromFile(url)) { - thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; - return; - } - } else { - std::string texture_data; - if(download_to_string_cache(url, texture_data, {}, program->get_current_plugin()->use_tor, true) != DownloadResult::OK || !thumbnail_data->image->loadFromMemory(texture_data.data(), texture_data.size())) { - thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; - return; - } - } - } - - if(thumbnail_resize_target_size.x != 0 && thumbnail_resize_target_size.y != 0) { - sf::Vector2u new_image_size = to_vec2u(clamp_to_size(to_vec2f(thumbnail_data->image->getSize()), to_vec2f(thumbnail_resize_target_size))); - if(new_image_size.x < thumbnail_data->image->getSize().x || new_image_size.y < thumbnail_data->image->getSize().y) { - auto destination_image = std::make_unique(); - copy_resize(*thumbnail_data->image, *destination_image, new_image_size); - thumbnail_data->image = std::move(destination_image); - save_image_as_thumbnail_atomic(*thumbnail_data->image, thumbnail_path, get_ext(url)); - thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; - return; - } - } - - thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; - return; - }); - } - void Body::draw(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size) { draw(window, pos, size, Json::nullValue); } @@ -404,11 +292,6 @@ namespace QuickMedia { num_visible_items = 0; last_item_fully_visible = true; - if(loading_thumbnail && load_thumbnail_future.valid() && load_thumbnail_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { - load_thumbnail_future.get(); - loading_thumbnail = false; - } - int num_items = items.size(); if(num_items == 0 || size.y <= 0.0f) { for(auto it = item_thumbnail_textures.begin(); it != item_thumbnail_textures.end();) { @@ -570,9 +453,8 @@ namespace QuickMedia { item_thumbnail->referenced = true; if(draw_thumbnails) { - if(!loading_thumbnail && !item->thumbnail_url.empty() && item_thumbnail->loading_state == LoadingState::NOT_LOADED) { - item_thumbnail->loading_state = LoadingState::LOADING; - load_thumbnail_from_url(item->thumbnail_url, item->thumbnail_is_local, thumbnail_resize_target_size, item_thumbnail); + if(!item->thumbnail_url.empty() && item_thumbnail->loading_state == LoadingState::NOT_LOADED) { + async_image_loader.load_thumbnail(item->thumbnail_url, item->thumbnail_is_local, thumbnail_resize_target_size, program->get_current_plugin()->use_tor, item_thumbnail); } if(item_thumbnail->loading_state == LoadingState::FINISHED_LOADING) { diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 709dfea..909bdd7 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -3502,6 +3502,20 @@ namespace QuickMedia { const float tab_vertical_offset = 10.0f; + sf::Text room_name_text("", *font, 18); + if(current_room_body_data) + room_name_text.setString(current_room_body_data->body_item->get_title()); + const float room_name_text_height = std::floor(room_name_text.getLocalBounds().height); + 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; + const float room_avatar_height = 32.0f; + + sf::Sprite room_avatar_sprite; + auto room_avatar_thumbnail_data = std::make_shared(); + AsyncImageLoader async_image_loader; + if(current_room_body_data) + async_image_loader.load_thumbnail(current_room_body_data->body_item->thumbnail_url, false, sf::Vector2i(), use_tor, room_avatar_thumbnail_data); + auto typing_async_func = [matrix](bool new_state, std::string room_id) { if(new_state) { matrix->on_start_typing(room_id); @@ -3755,7 +3769,7 @@ namespace QuickMedia { } //chat_input.on_event(event); chat_input.process_event(event); - } else if(tabs[selected_tab].type == ChatTabType::ROOMS && event.type == sf::Event::KeyReleased && event.key.code == sf::Keyboard::Enter) { + } else if(tabs[selected_tab].type == ChatTabType::ROOMS && event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Enter) { BodyItem *selected_item = tabs[selected_tab].body->get_selected(); if(selected_item) { current_room_id = selected_item->url; @@ -3773,8 +3787,11 @@ namespace QuickMedia { } auto room_body_item_it = body_items_by_room_id.find(current_room_id); - if(room_body_item_it != body_items_by_room_id.end()) + if(room_body_item_it != body_items_by_room_id.end()) { current_room_body_data = &room_body_item_it->second; + room_name_text.setString(current_room_body_data->body_item->get_title()); + room_avatar_thumbnail_data = std::make_shared(); + } } } } @@ -3782,8 +3799,10 @@ namespace QuickMedia { switch(new_page) { case Page::FILE_MANAGER: { new_page = Page::CHAT; - if(!file_manager) + if(!file_manager) { file_manager = new FileManager(); + file_manager->set_current_directory(get_home_dir().data); + } page_stack.push(Page::CHAT); current_page = Page::FILE_MANAGER; file_manager_page(); @@ -3839,7 +3858,28 @@ namespace QuickMedia { ++it; } - const float tab_shade_height = tab_spacer_height + std::floor(tab_vertical_offset) + tab_height + 10.0f; + async_image_loader.update(); + if(current_room_body_data && room_avatar_thumbnail_data->loading_state == LoadingState::NOT_LOADED) + async_image_loader.load_thumbnail(current_room_body_data->body_item->thumbnail_url, false, sf::Vector2i(), use_tor, room_avatar_thumbnail_data); + + if(room_avatar_thumbnail_data->loading_state == LoadingState::FINISHED_LOADING) { + if(!room_avatar_thumbnail_data->texture.loadFromImage(*room_avatar_thumbnail_data->image)) + fprintf(stderr, "Warning: failed to load texture for room avatar\n"); + room_avatar_thumbnail_data->image.reset(); + room_avatar_thumbnail_data->loading_state = LoadingState::APPLIED_TO_TEXTURE; + room_avatar_sprite.setTexture(room_avatar_thumbnail_data->texture, true); + + auto texture_size = room_avatar_sprite.getTexture()->getSize(); + if(texture_size.x > 0 && texture_size.y > 0) { + float width_ratio = (float)texture_size.x / (float)texture_size.y; + float height_scale = room_avatar_height / (float)texture_size.y; + float width_scale = height_scale * width_ratio; + room_avatar_sprite.setScale(width_scale, height_scale); + } + } + + const float room_name_padding_y = (selected_tab == MESSAGES_TAB_INDEX ? room_name_total_height : 0.0f); + const float tab_shade_height = tab_spacer_height + std::floor(tab_vertical_offset) + tab_height + 10.0f + room_name_padding_y; const float chat_height = chat_input.get_height(); if(std::abs(chat_height - prev_chat_height) > 1.0f) { @@ -3883,7 +3923,7 @@ namespace QuickMedia { if(!sync_running && sync_timer.getElapsedTime().asMilliseconds() >= sync_min_time_ms) { fprintf(stderr, "Time since last sync: %d ms\n", sync_timer.getElapsedTime().asMilliseconds()); // TODO: Ignore matrix->sync() call the first time, its already called above for the first time - sync_min_time_ms = 3000; + sync_min_time_ms = 50; sync_running = true; sync_timer.restart(); sync_future_room_id = current_room_id; @@ -3949,11 +3989,25 @@ namespace QuickMedia { url_selection_body.draw(window, body_pos, body_size); else tabs[selected_tab].body->draw(window, body_pos, body_size); - const float tab_y = tab_spacer_height + std::floor(tab_vertical_offset + tab_height * 0.5f - (tab_text_size + 5.0f) * 0.5f); + const float tab_y = tab_spacer_height + std::floor(tab_vertical_offset + tab_height * 0.5f - (tab_text_size + 5.0f) * 0.5f) + room_name_padding_y; tab_shade.setSize(sf::Vector2f(window_size.x, tab_shade_height)); window.draw(tab_shade); + if(tabs[selected_tab].type == ChatTabType::MESSAGES) { + float room_name_text_offset_x = 0.0f; + if(room_avatar_sprite.getTexture() && room_avatar_sprite.getTexture()->getNativeHandle() != 0) { + auto room_avatar_texture_size = room_avatar_sprite.getTexture()->getSize(); + 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); + 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); + window.draw(room_name_text); + } + gradient_points[0].position.x = 0.0f; gradient_points[0].position.y = tab_shade_height; @@ -3969,7 +4023,7 @@ namespace QuickMedia { int i = 0; for(ChatTab &tab : tabs) { if(i == selected_tab) { - tab_background.setPosition(std::floor(i * width_per_tab + tab_margin_x), tab_spacer_height + std::floor(tab_vertical_offset)); + tab_background.setPosition(std::floor(i * width_per_tab + tab_margin_x), tab_spacer_height + std::floor(tab_vertical_offset) + room_name_padding_y); window.draw(tab_background); } const float center = (i * width_per_tab) + (width_per_tab * 0.5f); @@ -4025,7 +4079,7 @@ namespace QuickMedia { current_room_body_data->body_item->title_color = sf::Color::White; current_room_body_data->last_message_read = true; } - } else { + } else if(current_room_body_data && !current_room_body_data->last_message_read) { window.draw(more_messages_below_rect); } } diff --git a/src/Storage.cpp b/src/Storage.cpp index f82aba3..cd34b56 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -27,6 +27,10 @@ namespace QuickMedia { passwd *pw = getpwuid(getuid()); homeDir = pw->pw_dir; } + if(!homeDir) { + fprintf(stderr, "Failed to get home directory of user!\n"); + abort(); + } return homeDir; #elif OS_FAMILY == OS_FAMILY_WINDOWS BOOL ret; diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 257feb4..6d89ff4 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -1174,6 +1174,7 @@ namespace QuickMedia { request_data["type"] = "m.login.password"; request_data["identifier"] = std::move(identifier_json); request_data["password"] = password; + request_data["initial_device_display_name"] = "QuickMedia"; // :^) Json::StreamWriterBuilder builder; builder["commentStyle"] = "None"; -- cgit v1.2.3