From b87866cb0b5fd40c11754769ab695997a79f11fe Mon Sep 17 00:00:00 2001 From: Green Sky Date: Sat, 22 Mar 2025 20:05:30 +0100 Subject: [PATCH] extract contact list sorting and refine sort --- src/CMakeLists.txt | 2 + src/chat_gui/contact_list_sorter.cpp | 138 +++++++++++++++++++++++++++ src/chat_gui/contact_list_sorter.hpp | 52 ++++++++++ src/chat_gui4.cpp | 60 +----------- src/chat_gui4.hpp | 3 +- 5 files changed, 199 insertions(+), 56 deletions(-) create mode 100644 src/chat_gui/contact_list_sorter.cpp create mode 100644 src/chat_gui/contact_list_sorter.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index db10498..5f04bfd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -89,6 +89,8 @@ target_sources(tomato PUBLIC ./chat_gui/icons/group.cpp ./chat_gui/contact_list.hpp ./chat_gui/contact_list.cpp + ./chat_gui/contact_list_sorter.hpp + ./chat_gui/contact_list_sorter.cpp ./chat_gui/file_selector.hpp ./chat_gui/file_selector.cpp ./chat_gui/image_viewer_popup.hpp diff --git a/src/chat_gui/contact_list_sorter.cpp b/src/chat_gui/contact_list_sorter.cpp new file mode 100644 index 0000000..e58e03c --- /dev/null +++ b/src/chat_gui/contact_list_sorter.cpp @@ -0,0 +1,138 @@ +#include "./contact_list_sorter.hpp" + +#include + +ContactListSorter::comperator_fn ContactListSorter::getSortGroupsOverPrivates(void) { + return [](const ContactRegistry4& cr, const Contact4 lhs, const Contact4 rhs) -> std::optional { + + // - 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; + } + + return std::nullopt; + }; +} + +ContactListSorter::comperator_fn ContactListSorter::getSortAcitivty(void) { + return [](const ContactRegistry4& cr, const Contact4 lhs, const Contact4 rhs) -> std::optional { + // - 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; + } + } + + return std::nullopt; + }; +} + +ContactListSorter::comperator_fn ContactListSorter::getSortFirstSeen(void) { + return [](const ContactRegistry4& cr, const Contact4 lhs, const Contact4 rhs) -> std::optional { + // - first seen (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 FirstSeen + + // - first seen 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; + } + } + + return std::nullopt; + }; +} + +bool ContactListSorter::resolveStack(const std::vector& stack, const ContactRegistry4& cr, const Contact4 lhs, const Contact4 rhs) { + for (const auto& fn : stack) { + auto res = fn(cr, lhs, rhs); + if (res.has_value()) { + return res.value(); + } + } + + // fallback to numberical ordering, making sure the ordering can be strong + return entt::to_integral(lhs) > entt::to_integral(rhs); +} + +ContactListSorter::ContactListSorter(ContactStore4I& cs) : + _cs(cs), _cs_sr(_cs.newSubRef(this)) +{ + _sort_stack.reserve(3); + _sort_stack.emplace_back(getSortGroupsOverPrivates()); + _sort_stack.emplace_back(getSortAcitivty()); + _sort_stack.emplace_back(getSortFirstSeen()); + + _cs_sr + .subscribe(ContactStore4_Event::contact_construct) + .subscribe(ContactStore4_Event::contact_update) + .subscribe(ContactStore4_Event::contact_destroy) + ; +} + +ContactListSorter::~ContactListSorter(void) { +} + +void ContactListSorter::sort(void) { + // TODO: timer + if (!_dirty) { + return; + } + + 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 + cr.clear(); + for (const auto cv : cr.view()) { + (void)cr.get_or_emplace(cv); + } + + // second: sort + cr.sort( + [this, &cr](const Contact4 lhs, const Contact4 rhs) -> bool { + return resolveStack(_sort_stack, cr, lhs, rhs); + }, + entt::insertion_sort{} // o(n) in >90% of cases + ); + + _dirty = false; +} + +bool ContactListSorter::onEvent(const ContactStore::Events::Contact4Construct&) { + _dirty = true; + return false; +} + +bool ContactListSorter::onEvent(const ContactStore::Events::Contact4Update&) { + _dirty = true; + return false; +} + +bool ContactListSorter::onEvent(const ContactStore::Events::Contact4Destory&) { + _dirty = true; + return false; +} diff --git a/src/chat_gui/contact_list_sorter.hpp b/src/chat_gui/contact_list_sorter.hpp new file mode 100644 index 0000000..5505e0b --- /dev/null +++ b/src/chat_gui/contact_list_sorter.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +#include "./contact_list.hpp" + +#include +#include +#include + +namespace Contact::Components { + + // empty contact comp that is sorted in the set for displaying + struct ContactSortTag {}; + +} // Contact::Components + + +class ContactListSorter : public ContactStore4EventI { + public: + using comperator_fn = std::function(const ContactRegistry4& cr, const Contact4 lhs, const Contact4 rhs)>; + + static comperator_fn getSortGroupsOverPrivates(void); + static comperator_fn getSortAcitivty(void); + static comperator_fn getSortFirstSeen(void); + + private: + ContactStore4I& _cs; + ContactStore4I::SubscriptionReference _cs_sr; + std::vector _sort_stack; + + bool _dirty {true}; + // TODO: timer, to guarantie a sort ever X seconds? + // (turns out we dont throw on new messages <.<) + + private: + static bool resolveStack(const std::vector& stack, const ContactRegistry4& cr, const Contact4 lhs, const Contact4 rhs); + + public: + // TODO: expose sort stack + ContactListSorter(ContactStore4I& cs); + ~ContactListSorter(void); + + // optionally perform the sort + void sort(void); + + protected: + bool onEvent(const ContactStore::Events::Contact4Construct&) override; + bool onEvent(const ContactStore::Events::Contact4Update&) override; + bool onEvent(const ContactStore::Events::Contact4Destory&) override; +}; diff --git a/src/chat_gui4.cpp b/src/chat_gui4.cpp index aa51d6e..58bac4f 100644 --- a/src/chat_gui4.cpp +++ b/src/chat_gui4.cpp @@ -62,9 +62,6 @@ namespace Components { int tm_min {0}; }; - // empty contact comp that is sorted in the set for displaying - struct ContactSortTag {}; - } // Components namespace Context { @@ -207,7 +204,8 @@ ChatGui4::ChatGui4( _b_tc(_bil, tu), _theme(theme), _sip(tu), - _ivp(_msg_tc) + _ivp(_msg_tc), + _cls(cs) { _os_sr.subscribe(ObjectStore_Event::object_update); } @@ -258,57 +256,9 @@ float ChatGui4::render(float time_delta, bool window_hidden, bool window_focused } renderContactList(); + // after vis check 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 - ); + _cls.sort(); } ImGui::SameLine(); @@ -1715,7 +1665,7 @@ void ChatGui4::renderContactList(void) { _rmm, _theme, _contact_tc, - contact_const_runtime_view{}.iterate(cr.storage()), + contact_const_runtime_view{}.iterate(cr.storage()), selected_contact )) { _selected_contact = selected_contact.entity(); diff --git a/src/chat_gui4.hpp b/src/chat_gui4.hpp index 9e8ff12..db6aadd 100644 --- a/src/chat_gui4.hpp +++ b/src/chat_gui4.hpp @@ -15,6 +15,7 @@ #include "./chat_gui/file_selector.hpp" #include "./chat_gui/send_image_popup.hpp" #include "./chat_gui/image_viewer_popup.hpp" +#include "./chat_gui/contact_list_sorter.hpp" #include @@ -41,9 +42,9 @@ class ChatGui4 : public ObjectStoreEventI { FileSelector _fss; SendImagePopup _sip; ImageViewerPopup _ivp; + ContactListSorter _cls; // set to true if not hovered - // TODO: add timer? bool _contact_list_sortable {false}; // TODO: refactor this to allow multiple open contacts