From 1fb590dfc18578b2bc433078cec1b87d4420216b Mon Sep 17 00:00:00 2001 From: Green Sky Date: Fri, 21 Mar 2025 16:16:02 +0100 Subject: [PATCH] chat gui refactor and first contact sorting --- src/chat_gui/contact_list.cpp | 36 ++++++ src/chat_gui/contact_list.hpp | 16 +++ src/chat_gui4.cpp | 206 +++++++++++++++++++++++----------- src/chat_gui4.hpp | 7 ++ 4 files changed, 200 insertions(+), 65 deletions(-) diff --git a/src/chat_gui/contact_list.cpp b/src/chat_gui/contact_list.cpp index 4d06b77..6fee712 100644 --- a/src/chat_gui/contact_list.cpp +++ b/src/chat_gui/contact_list.cpp @@ -1,12 +1,15 @@ #include "./contact_list.hpp" #include +#include #include #include #include //#include +#include + #include "./icons/direct.hpp" #include "./icons/cloud.hpp" #include "./icons/mail.hpp" @@ -312,3 +315,36 @@ bool renderContactBig( return got_selected; } +bool renderContactList( + ContactRegistry4& cr, + RegistryMessageModelI& rmm, + const Theme& th, + ContactTextureCache& contact_tc, + const contact_const_runtime_view& view, + + // in/out + ContactHandle4& selected_c +) { + bool selection_changed {false}; + for (const Contact4 cv : view) { + ContactHandle4 c{cr, cv}; + const bool selected = selected_c == c; + + // TODO: is there a better way? + // maybe cache mm? + bool has_unread = false; + if (const auto* mm = rmm.get(c); mm != nullptr) { + if (const auto* unread_storage = mm->storage(); unread_storage != nullptr && !unread_storage->empty()) { + has_unread = true; + } + } + + // TODO: expose line_height + if (renderContactBig(th, contact_tc, c, 2, has_unread, true, selected)) { + selected_c = c; + selection_changed = true; + } + } + return selection_changed; +} + diff --git a/src/chat_gui/contact_list.hpp b/src/chat_gui/contact_list.hpp index 3aaf998..c0ba408 100644 --- a/src/chat_gui/contact_list.hpp +++ b/src/chat_gui/contact_list.hpp @@ -46,3 +46,19 @@ bool renderContactBig( const bool selected = false ); +using contact_sparse_set = entt::basic_sparse_set; +using contact_runtime_view = entt::basic_runtime_view; +using contact_const_runtime_view = entt::basic_runtime_view; + +// returns true if contact was selected +bool renderContactList( + ContactRegistry4& cr, + RegistryMessageModelI& rmm, + const Theme& th, + ContactTextureCache& contact_tc, + const contact_const_runtime_view& view, + + // in/out + ContactHandle4& selected_c +); + diff --git a/src/chat_gui4.cpp b/src/chat_gui4.cpp index 7e27733..aa51d6e 100644 --- a/src/chat_gui4.cpp +++ b/src/chat_gui4.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -43,6 +44,7 @@ #include #include +// TODO: split into msg and c namespace Components { struct UnreadFade { @@ -60,6 +62,9 @@ namespace Components { int tm_min {0}; }; + // empty contact comp that is sorted in the set for displaying + struct ContactSortTag {}; + } // Components namespace Context { @@ -253,6 +258,58 @@ float ChatGui4::render(float time_delta, bool window_hidden, bool window_focused } renderContactList(); + if (_contact_list_sortable) { + // TODO: extract this; with timer and events to dirty + // !! events !! + auto& cr = _cs.registry(); + + // first: make sure every cantact we want to have in the list has the tag + // do we pass exclusion to the list widget, or update sort comp? - the later + // TODO: re do from sratch every time? + //cr.clear(); + for (const auto cv : cr.view()) { + (void)cr.get_or_emplace(cv); + } + + // second: sort + cr.sort( + [&](const Contact4 lhs, const Contact4 rhs) -> bool { + // TODO: custom sort rules, order + + // - groups (> privates) + if (cr.all_of(lhs) && !cr.all_of(rhs)) { + return true; + } else if (!cr.all_of(lhs) && cr.all_of(rhs)) { + return false; + } + + // - activity (exists) + if (cr.all_of(lhs) && !cr.all_of(rhs)) { + return true; + } else if (!cr.all_of(lhs) && cr.all_of(rhs)) { + return false; + } + // else - we can assume both have or dont have LastActivity + + // - activity new > old + if (cr.all_of(lhs)) { + const auto l = cr.get(lhs).ts; + const auto r = cr.get(rhs).ts; + if (l > r) { + return true; + } else if (l < r) { + return false; + } + } + + // - first seen new > old + // TODO: implement + + return false; + }, + entt::insertion_sort{} // o(n) in 99% of cases + ); + } ImGui::SameLine(); if (_selected_contact) { @@ -968,10 +1025,72 @@ float ChatGui4::render(float time_delta, bool window_hidden, bool window_focused return 1000.f; // TODO: higher min fps? } -void ChatGui4::sendFilePath(std::string_view file_path) { +void ChatGui4::sendFilePath(Contact4 c, std::string_view file_path) { + if (c == entt::null) { + return; + } + const auto path = std::filesystem::path(file_path); - if (_selected_contact && std::filesystem::is_regular_file(path)) { - _rmm.sendFilePath(*_selected_contact, path.filename().generic_u8string(), path.generic_u8string()); + if (!std::filesystem::is_regular_file(path)) { + return; + } + + _rmm.sendFilePath(c, path.filename().generic_u8string(), path.generic_u8string()); +} + +void ChatGui4::sendFileList(Contact4 c, const std::vector& list) { + // TODO: file collection sip + if (list.size() > 1) { + for (const auto it : list) { + sendFilePath(c, it); + } + } else if (list.size() == 1) { + const auto path = std::filesystem::path(list.front()); + if (std::filesystem::is_regular_file(path)) { + if (!_sip.sendFilePath( + list.front(), + [this, c](const auto& img_data, const auto file_ext) { + // create file name + // TODO: only create file if changed or from memory + // TODO: move this into sip + std::ostringstream tmp_file_name {"tomato_Image_", std::ios_base::ate}; + { + const auto now = std::chrono::system_clock::now(); + const auto ctime = std::chrono::system_clock::to_time_t(now); + tmp_file_name + << std::put_time(std::localtime(&ctime), "%F_%H-%M-%S") + << "." + << std::setfill('0') << std::setw(3) + << std::chrono::duration_cast(now.time_since_epoch() - std::chrono::duration_cast(now.time_since_epoch())).count() + << file_ext + ; + } + + std::cout << "tmp image path " << tmp_file_name.str() << "\n"; + + const std::filesystem::path tmp_send_file_path = "tmp_send_files"; + std::filesystem::create_directories(tmp_send_file_path); + const auto tmp_file_path = tmp_send_file_path / tmp_file_name.str(); + + std::ofstream(tmp_file_path, std::ios_base::out | std::ios_base::binary) + .write(reinterpret_cast(img_data.data()), img_data.size()); + + _rmm.sendFilePath(c, tmp_file_name.str(), tmp_file_path.generic_u8string()); + }, + [](){} + )) { + // if sip fails to open the file + sendFilePath(c, list.front()); + } + } else { + // if not file (???) + } + } +} + +void ChatGui4::sendFilePath(std::string_view file_path) { + if (_selected_contact) { + sendFilePath(*_selected_contact, file_path); } } @@ -1584,23 +1703,22 @@ void ChatGui4::renderMessageExtra(Message3Registry& reg, const Message3 e) { void ChatGui4::renderContactList(void) { if (ImGui::BeginChild("contacts", {TEXT_BASE_WIDTH*35, 0})) { + _contact_list_sortable = !ImGui::IsWindowHovered(); + auto& cr = _cs.registry(); - //for (const auto& c : _cm.getBigContacts()) { - for (const auto& c : cr.view()) { - const bool selected = _selected_contact.has_value() && *_selected_contact == c; - - // TODO: is there a better way? - // maybe cache mm? - bool has_unread = false; - if (const auto* mm = _rmm.get(c); mm != nullptr) { - if (const auto* unread_storage = mm->storage(); unread_storage != nullptr && !unread_storage->empty()) { - has_unread = true; - } - } - - if (renderContactBig(_theme, _contact_tc, {cr, c}, 2, has_unread, true, selected)) { - _selected_contact = c; - } + ContactHandle4 selected_contact{}; + if (_selected_contact.has_value()) { + selected_contact = {cr, *_selected_contact}; + } + if (::renderContactList( + cr, + _rmm, + _theme, + _contact_tc, + contact_const_runtime_view{}.iterate(cr.storage()), + selected_contact + )) { + _selected_contact = selected_contact.entity(); } } ImGui::EndChild(); @@ -1730,53 +1848,11 @@ void ChatGui4::pasteFile(const char* mime_type) { } void ChatGui4::sendFileList(const std::vector& list) { - // TODO: file collection sip - if (list.size() > 1) { - for (const auto it : list) { - sendFilePath(it); - } - } else if (list.size() == 1) { - const auto path = std::filesystem::path(list.front()); - if (std::filesystem::is_regular_file(path)) { - if (!_sip.sendFilePath( - list.front(), - [this](const auto& img_data, const auto file_ext) { - // create file name - // TODO: only create file if changed or from memory - // TODO: move this into sip - std::ostringstream tmp_file_name {"tomato_Image_", std::ios_base::ate}; - { - const auto now = std::chrono::system_clock::now(); - const auto ctime = std::chrono::system_clock::to_time_t(now); - tmp_file_name - << std::put_time(std::localtime(&ctime), "%F_%H-%M-%S") - << "." - << std::setfill('0') << std::setw(3) - << std::chrono::duration_cast(now.time_since_epoch() - std::chrono::duration_cast(now.time_since_epoch())).count() - << file_ext - ; - } - - std::cout << "tmp image path " << tmp_file_name.str() << "\n"; - - const std::filesystem::path tmp_send_file_path = "tmp_send_files"; - std::filesystem::create_directories(tmp_send_file_path); - const auto tmp_file_path = tmp_send_file_path / tmp_file_name.str(); - - std::ofstream(tmp_file_path, std::ios_base::out | std::ios_base::binary) - .write(reinterpret_cast(img_data.data()), img_data.size()); - - _rmm.sendFilePath(*_selected_contact, tmp_file_name.str(), tmp_file_path.generic_u8string()); - }, - [](){} - )) { - // if sip fails to open the file - sendFilePath(list.front()); - } - } else { - // if not file (???) - } + if (!_selected_contact) { + return; } + + sendFileList(*_selected_contact, list); } bool ChatGui4::onEvent(const ObjectStore::Events::ObjectUpdate& e) { diff --git a/src/chat_gui4.hpp b/src/chat_gui4.hpp index 43c2ecf..9e8ff12 100644 --- a/src/chat_gui4.hpp +++ b/src/chat_gui4.hpp @@ -42,6 +42,10 @@ class ChatGui4 : public ObjectStoreEventI { SendImagePopup _sip; ImageViewerPopup _ivp; + // set to true if not hovered + // TODO: add timer? + bool _contact_list_sortable {false}; + // TODO: refactor this to allow multiple open contacts std::optional _selected_contact; @@ -77,6 +81,9 @@ class ChatGui4 : public ObjectStoreEventI { float render(float time_delta, bool window_hidden, bool window_focused); public: + void sendFilePath(Contact4 c, std::string_view file_path); + void sendFileList(Contact4 c, const std::vector& list); + void sendFilePath(std::string_view file_path); void sendFileList(const std::vector& list);