chat gui refactor and first contact sorting
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
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:
parent
c383c4f5a0
commit
1fb590dfc1
@ -1,12 +1,15 @@
|
|||||||
#include "./contact_list.hpp"
|
#include "./contact_list.hpp"
|
||||||
|
|
||||||
#include <solanaceae/contact/components.hpp>
|
#include <solanaceae/contact/components.hpp>
|
||||||
|
#include <solanaceae/message3/components.hpp>
|
||||||
#include <solanaceae/util/utils.hpp>
|
#include <solanaceae/util/utils.hpp>
|
||||||
#include <solanaceae/util/time.hpp>
|
#include <solanaceae/util/time.hpp>
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
#include <imgui/imgui.h>
|
||||||
//#include <imgui/imgui_internal.h>
|
//#include <imgui/imgui_internal.h>
|
||||||
|
|
||||||
|
#include <entt/entity/runtime_view.hpp>
|
||||||
|
|
||||||
#include "./icons/direct.hpp"
|
#include "./icons/direct.hpp"
|
||||||
#include "./icons/cloud.hpp"
|
#include "./icons/cloud.hpp"
|
||||||
#include "./icons/mail.hpp"
|
#include "./icons/mail.hpp"
|
||||||
@ -312,3 +315,36 @@ bool renderContactBig(
|
|||||||
return got_selected;
|
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
|
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 <solanaceae/tox_contacts/components.hpp>
|
||||||
|
|
||||||
#include <entt/entity/entity.hpp>
|
#include <entt/entity/entity.hpp>
|
||||||
|
#include <entt/entity/runtime_view.hpp>
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
#include <imgui/imgui.h>
|
||||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||||
@ -43,6 +44,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
|
// TODO: split into msg and c
|
||||||
namespace Components {
|
namespace Components {
|
||||||
|
|
||||||
struct UnreadFade {
|
struct UnreadFade {
|
||||||
@ -60,6 +62,9 @@ namespace Components {
|
|||||||
int tm_min {0};
|
int tm_min {0};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// empty contact comp that is sorted in the set for displaying
|
||||||
|
struct ContactSortTag {};
|
||||||
|
|
||||||
} // Components
|
} // Components
|
||||||
|
|
||||||
namespace Context {
|
namespace Context {
|
||||||
@ -253,6 +258,58 @@ float ChatGui4::render(float time_delta, bool window_hidden, bool window_focused
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderContactList();
|
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();
|
ImGui::SameLine();
|
||||||
|
|
||||||
if (_selected_contact) {
|
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?
|
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);
|
const auto path = std::filesystem::path(file_path);
|
||||||
if (_selected_contact && std::filesystem::is_regular_file(path)) {
|
if (!std::filesystem::is_regular_file(path)) {
|
||||||
_rmm.sendFilePath(*_selected_contact, path.filename().generic_u8string(), path.generic_u8string());
|
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) {
|
void ChatGui4::renderContactList(void) {
|
||||||
if (ImGui::BeginChild("contacts", {TEXT_BASE_WIDTH*35, 0})) {
|
if (ImGui::BeginChild("contacts", {TEXT_BASE_WIDTH*35, 0})) {
|
||||||
|
_contact_list_sortable = !ImGui::IsWindowHovered();
|
||||||
|
|
||||||
auto& cr = _cs.registry();
|
auto& cr = _cs.registry();
|
||||||
//for (const auto& c : _cm.getBigContacts()) {
|
ContactHandle4 selected_contact{};
|
||||||
for (const auto& c : cr.view<Contact::Components::TagBig>()) {
|
if (_selected_contact.has_value()) {
|
||||||
const bool selected = _selected_contact.has_value() && *_selected_contact == c;
|
selected_contact = {cr, *_selected_contact};
|
||||||
|
}
|
||||||
// TODO: is there a better way?
|
if (::renderContactList(
|
||||||
// maybe cache mm?
|
cr,
|
||||||
bool has_unread = false;
|
_rmm,
|
||||||
if (const auto* mm = _rmm.get(c); mm != nullptr) {
|
_theme,
|
||||||
if (const auto* unread_storage = mm->storage<Message::Components::TagUnread>(); unread_storage != nullptr && !unread_storage->empty()) {
|
_contact_tc,
|
||||||
has_unread = true;
|
contact_const_runtime_view{}.iterate(cr.storage<Components::ContactSortTag>()),
|
||||||
}
|
selected_contact
|
||||||
}
|
)) {
|
||||||
|
_selected_contact = selected_contact.entity();
|
||||||
if (renderContactBig(_theme, _contact_tc, {cr, c}, 2, has_unread, true, selected)) {
|
|
||||||
_selected_contact = c;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
@ -1730,53 +1848,11 @@ void ChatGui4::pasteFile(const char* mime_type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ChatGui4::sendFileList(const std::vector<std::string_view>& list) {
|
void ChatGui4::sendFileList(const std::vector<std::string_view>& list) {
|
||||||
// TODO: file collection sip
|
if (!_selected_contact) {
|
||||||
if (list.size() > 1) {
|
return;
|
||||||
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 (???)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendFileList(*_selected_contact, list);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChatGui4::onEvent(const ObjectStore::Events::ObjectUpdate& e) {
|
bool ChatGui4::onEvent(const ObjectStore::Events::ObjectUpdate& e) {
|
||||||
|
@ -42,6 +42,10 @@ class ChatGui4 : public ObjectStoreEventI {
|
|||||||
SendImagePopup _sip;
|
SendImagePopup _sip;
|
||||||
ImageViewerPopup _ivp;
|
ImageViewerPopup _ivp;
|
||||||
|
|
||||||
|
// set to true if not hovered
|
||||||
|
// TODO: add timer?
|
||||||
|
bool _contact_list_sortable {false};
|
||||||
|
|
||||||
// TODO: refactor this to allow multiple open contacts
|
// TODO: refactor this to allow multiple open contacts
|
||||||
std::optional<Contact4> _selected_contact;
|
std::optional<Contact4> _selected_contact;
|
||||||
|
|
||||||
@ -77,6 +81,9 @@ class ChatGui4 : public ObjectStoreEventI {
|
|||||||
float render(float time_delta, bool window_hidden, bool window_focused);
|
float render(float time_delta, bool window_hidden, bool window_focused);
|
||||||
|
|
||||||
public:
|
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 sendFilePath(std::string_view file_path);
|
||||||
void sendFileList(const std::vector<std::string_view>& list);
|
void sendFileList(const std::vector<std::string_view>& list);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user