extract contact list sorting and refine sort
Some checks are pending
ContinuousDelivery / linux-ubuntu (push) Waiting to run
ContinuousDelivery / android (map[ndk_abi:arm64-v8a vcpkg_toolkit:arm64-android]) (push) Waiting to run
ContinuousDelivery / android (map[ndk_abi:armeabi-v7a vcpkg_toolkit:arm-neon-android]) (push) Waiting to run
ContinuousDelivery / android (map[ndk_abi:x86_64 vcpkg_toolkit:x64-android]) (push) Waiting to run
ContinuousDelivery / windows (push) Waiting to run
ContinuousDelivery / windows-asan (push) Waiting to run
ContinuousDelivery / dumpsyms (push) Blocked by required conditions
ContinuousDelivery / release (push) Blocked by required conditions
ContinuousIntegration / linux (push) Waiting to run
ContinuousIntegration / android (map[ndk_abi:arm64-v8a vcpkg_toolkit:arm64-android]) (push) Waiting to run
ContinuousIntegration / android (map[ndk_abi:armeabi-v7a vcpkg_toolkit:arm-neon-android]) (push) Waiting to run
ContinuousIntegration / android (map[ndk_abi:x86_64 vcpkg_toolkit:x64-android]) (push) Waiting to run
ContinuousIntegration / macos (push) Waiting to run
ContinuousIntegration / windows (push) Waiting to run

This commit is contained in:
Green Sky 2025-03-22 20:05:30 +01:00
parent 0f85bcc128
commit b87866cb0b
No known key found for this signature in database
GPG Key ID: DBE05085D874AB4A
5 changed files with 199 additions and 56 deletions

View File

@ -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

View File

@ -0,0 +1,138 @@
#include "./contact_list_sorter.hpp"
#include <solanaceae/contact/components.hpp>
ContactListSorter::comperator_fn ContactListSorter::getSortGroupsOverPrivates(void) {
return [](const ContactRegistry4& cr, const Contact4 lhs, const Contact4 rhs) -> std::optional<bool> {
// - groups (> privates)
if (cr.all_of<Contact::Components::TagGroup>(lhs) && !cr.all_of<Contact::Components::TagGroup>(rhs)) {
return true;
} else if (!cr.all_of<Contact::Components::TagGroup>(lhs) && cr.all_of<Contact::Components::TagGroup>(rhs)) {
return false;
}
return std::nullopt;
};
}
ContactListSorter::comperator_fn ContactListSorter::getSortAcitivty(void) {
return [](const ContactRegistry4& cr, const Contact4 lhs, const Contact4 rhs) -> std::optional<bool> {
// - activity (exists)
if (cr.all_of<Contact::Components::LastActivity>(lhs) && !cr.all_of<Contact::Components::LastActivity>(rhs)) {
return true;
} else if (!cr.all_of<Contact::Components::LastActivity>(lhs) && cr.all_of<Contact::Components::LastActivity>(rhs)) {
return false;
}
// else - we can assume both have or dont have LastActivity
// - activity new > old
if (cr.all_of<Contact::Components::LastActivity>(lhs)) {
const auto l = cr.get<Contact::Components::LastActivity>(lhs).ts;
const auto r = cr.get<Contact::Components::LastActivity>(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<bool> {
// - first seen (exists)
if (cr.all_of<Contact::Components::FirstSeen>(lhs) && !cr.all_of<Contact::Components::FirstSeen>(rhs)) {
return true;
} else if (!cr.all_of<Contact::Components::FirstSeen>(lhs) && cr.all_of<Contact::Components::FirstSeen>(rhs)) {
return false;
}
// else - we can assume both have or dont have FirstSeen
// - first seen new > old
if (cr.all_of<Contact::Components::FirstSeen>(lhs)) {
const auto l = cr.get<Contact::Components::FirstSeen>(lhs).ts;
const auto r = cr.get<Contact::Components::FirstSeen>(rhs).ts;
if (l > r) {
return true;
} else if (l < r) {
return false;
}
}
return std::nullopt;
};
}
bool ContactListSorter::resolveStack(const std::vector<comperator_fn>& 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<Contact::Components::ContactSortTag>();
for (const auto cv : cr.view<Contact::Components::TagBig>()) {
(void)cr.get_or_emplace<Contact::Components::ContactSortTag>(cv);
}
// second: sort
cr.sort<Contact::Components::ContactSortTag>(
[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;
}

View File

@ -0,0 +1,52 @@
#pragma once
#include <solanaceae/contact/contact_store_events.hpp>
#include <solanaceae/contact/contact_store_i.hpp>
#include "./contact_list.hpp"
#include <functional>
#include <optional>
#include <vector>
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<std::optional<bool>(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<comperator_fn> _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<comperator_fn>& 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;
};

View File

@ -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<Components::ContactSortTag>();
for (const auto cv : cr.view<Contact::Components::TagBig>()) {
(void)cr.get_or_emplace<Components::ContactSortTag>(cv);
}
// second: sort
cr.sort<Components::ContactSortTag>(
[&](const Contact4 lhs, const Contact4 rhs) -> bool {
// TODO: custom sort rules, order
// - groups (> privates)
if (cr.all_of<Contact::Components::TagGroup>(lhs) && !cr.all_of<Contact::Components::TagGroup>(rhs)) {
return true;
} else if (!cr.all_of<Contact::Components::TagGroup>(lhs) && cr.all_of<Contact::Components::TagGroup>(rhs)) {
return false;
}
// - activity (exists)
if (cr.all_of<Contact::Components::LastActivity>(lhs) && !cr.all_of<Contact::Components::LastActivity>(rhs)) {
return true;
} else if (!cr.all_of<Contact::Components::LastActivity>(lhs) && cr.all_of<Contact::Components::LastActivity>(rhs)) {
return false;
}
// else - we can assume both have or dont have LastActivity
// - activity new > old
if (cr.all_of<Contact::Components::LastActivity>(lhs)) {
const auto l = cr.get<Contact::Components::LastActivity>(lhs).ts;
const auto r = cr.get<Contact::Components::LastActivity>(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<Components::ContactSortTag>()),
contact_const_runtime_view{}.iterate(cr.storage<Contact::Components::ContactSortTag>()),
selected_contact
)) {
_selected_contact = selected_contact.entity();

View File

@ -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 <entt/container/dense_map.hpp>
@ -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