chat gui refactor and first contact sorting

This commit is contained in:
Green Sky 2025-03-21 16:16:02 +01:00
parent c383c4f5a0
commit 1fb590dfc1
No known key found for this signature in database
GPG Key ID: DBE05085D874AB4A
4 changed files with 200 additions and 65 deletions

@ -1,12 +1,15 @@
#include "./contact_list.hpp"
#include <solanaceae/contact/components.hpp>
#include <solanaceae/message3/components.hpp>
#include <solanaceae/util/utils.hpp>
#include <solanaceae/util/time.hpp>
#include <imgui/imgui.h>
//#include <imgui/imgui_internal.h>
#include <entt/entity/runtime_view.hpp>
#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<Message::Components::TagUnread>(); 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;
}

@ -46,3 +46,19 @@ bool renderContactBig(
const bool selected = false
);
using contact_sparse_set = entt::basic_sparse_set<Contact4>;
using contact_runtime_view = entt::basic_runtime_view<contact_sparse_set>;
using contact_const_runtime_view = entt::basic_runtime_view<const contact_sparse_set>;
// 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
);

@ -17,6 +17,7 @@
#include <solanaceae/tox_contacts/components.hpp>
#include <entt/entity/entity.hpp>
#include <entt/entity/runtime_view.hpp>
#include <imgui/imgui.h>
#include <imgui/misc/cpp/imgui_stdlib.h>
@ -43,6 +44,7 @@
#include <string>
#include <variant>
// 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<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
);
}
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<std::string_view>& 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<std::chrono::milliseconds>(now.time_since_epoch() - std::chrono::duration_cast<std::chrono::seconds>(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<const char*>(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<Contact::Components::TagBig>()) {
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<Message::Components::TagUnread>(); 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<Components::ContactSortTag>()),
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<std::string_view>& 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<std::chrono::milliseconds>(now.time_since_epoch() - std::chrono::duration_cast<std::chrono::seconds>(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<const char*>(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) {

@ -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<Contact4> _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<std::string_view>& list);
void sendFilePath(std::string_view file_path);
void sendFileList(const std::vector<std::string_view>& list);