From 4cd295065b83f7de5dd9ed21af946ec5d6559755 Mon Sep 17 00:00:00 2001 From: Green Sky Date: Fri, 28 Jul 2023 18:03:45 +0200 Subject: [PATCH] add chat gui, probably works --- src/CMakeLists.txt | 3 + src/chat_gui4.cpp | 504 ++++++++++++++++++++++++++++++++++++++++++++ src/chat_gui4.hpp | 45 ++++ src/main_screen.cpp | 5 +- src/main_screen.hpp | 2 + 5 files changed, 558 insertions(+), 1 deletion(-) create mode 100644 src/chat_gui4.cpp create mode 100644 src/chat_gui4.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4f30e403..6680096b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,6 +19,9 @@ add_executable(tomato ./sdlrenderer_texture_uploader.cpp ./file_selector.hpp ./file_selector.cpp + + ./chat_gui4.hpp + ./chat_gui4.cpp ) target_compile_features(tomato PUBLIC cxx_std_17) diff --git a/src/chat_gui4.cpp b/src/chat_gui4.cpp new file mode 100644 index 00000000..1776f8f3 --- /dev/null +++ b/src/chat_gui4.cpp @@ -0,0 +1,504 @@ +#include "./chat_gui4.hpp" + +#include "./file_selector.hpp" + +#include +#include +#include + +//#include "./media_meta_info_loader.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include + +ChatGui4::ChatGui4( + RegistryMessageModel& rmm, + Contact3Registry& cr, + TextureUploaderI& tu +) : _rmm(rmm), _cr(cr) { +} + +void ChatGui4::render(void) { + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->WorkPos); + ImGui::SetNextWindowSize(viewport->WorkSize); + + TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x; + TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing(); + + constexpr auto bg_window_flags = + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_MenuBar | + ImGuiWindowFlags_NoBringToFrontOnFocus; + + if (ImGui::Begin("tomato", nullptr, bg_window_flags)) { + if (ImGui::BeginMenuBar()) { + //ImGui::Separator(); + ImGui::Text("%.1fFPS", ImGui::GetIO().Framerate); + ImGui::EndMenuBar(); + } + + renderContactList(); + ImGui::SameLine(); + + if (_selected_contact) { + const std::string chat_label = "chat " + std::to_string(entt::to_integral(*_selected_contact)); + if (ImGui::BeginChild(chat_label.c_str(), {0, 0}, true)) { + static std::string text_buffer; + + if (_cr.all_of(*_selected_contact)) { + const auto sub_contacts = _cr.get(*_selected_contact).subs; + if (!sub_contacts.empty()) { + if (ImGui::BeginChild("subcontacts", {150, -100}, true)) { + ImGui::Text("subs: %zu", sub_contacts.size()); + ImGui::Separator(); + for (const auto& c : sub_contacts) { + if (renderSubContactListContact(c)) { + text_buffer.insert(0, (_cr.all_of(c) ? _cr.get(c).name : "") + ": "); + } + } + } + ImGui::EndChild(); + ImGui::SameLine(); + } + } + + if (ImGui::BeginChild("message_log", {0, -100}, false, ImGuiWindowFlags_MenuBar)) { + if (ImGui::BeginMenuBar()) { + ImGui::Checkbox("show extra info", &_show_chat_extra_info); + ImGui::EndMenuBar(); + } + + auto* msg_reg_ptr = _rmm.get(*_selected_contact); + + constexpr ImGuiTableFlags table_flags = + ImGuiTableFlags_BordersInnerV | + ImGuiTableFlags_RowBg | + ImGuiTableFlags_SizingFixedFit + ; + if (msg_reg_ptr != nullptr && ImGui::BeginTable("chat_table", 4, table_flags)) { + ImGui::TableSetupColumn("name", 0, TEXT_BASE_WIDTH * 15.f); + ImGui::TableSetupColumn("message", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("timestamp"); + ImGui::TableSetupColumn("extra_info", _show_chat_extra_info ? ImGuiTableColumnFlags_None : ImGuiTableColumnFlags_Disabled); + + // very hacky, and we have variable hight entries + //ImGuiListClipper clipper; + + // fake empty placeholders + // TODO: save/calc height for each row + // - use number of lines for text + // - save img dims (capped) + // - other static sizes + //ImGui::TableNextRow(0, TEXT_BASE_HEIGHT); + //ImGui::TableNextRow(0, TEXT_BASE_HEIGHT); + + Message3Registry& msg_reg = *msg_reg_ptr; + msg_reg.view() + .use() + .each([&](const Message3 e, Message::Components::ContactFrom& c_from, Message::Components::ContactTo& c_to, Message::Components::Timestamp ts + ) { + // TODO: why? + ImGui::TableNextRow(0, TEXT_BASE_HEIGHT); + + ImGui::PushID(entt::to_integral(e)); + + // name + if (ImGui::TableNextColumn()) { + if (_cr.all_of(c_from.c)) { + ImGui::TextUnformatted(_cr.get(c_from.c).name.c_str()); + } else { + ImGui::TextUnformatted(""); + } + + // highlight self + if (_cr.any_of(c_from.c)) { + ImU32 cell_bg_color = ImGui::GetColorU32(ImVec4(0.3f, 0.7f, 0.3f, 0.20f)); + ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, cell_bg_color); + } else { + //based on power level? + //ImU32 cell_bg_color = ImGui::GetColorU32(ImVec4(0.3f, 0.7f, 0.3f, 0.65f)); + //ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, cell_bg_color); + } + + // private group message + if (_cr.any_of(c_to.c)) { + ImU32 row_bg_color = ImGui::GetColorU32(ImVec4(0.5f, 0.2f, 0.5f, 0.35f)); + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, row_bg_color); + } + } + + // content (msgtext/file) + ImGui::TableNextColumn(); + if (msg_reg.all_of(e)) { + renderMessageBodyText(msg_reg, e); + } else if (msg_reg.any_of(e)) { // add more comps? + renderMessageBodyFile(msg_reg, e); + } else { + ImGui::TextUnformatted("---"); + } + + // ts + if (ImGui::TableNextColumn()) { + auto time = std::chrono::system_clock::to_time_t( + std::chrono::time_point{std::chrono::milliseconds{ts.ts}} + ); + auto localtime = std::localtime(&time); + + ImGui::Text("%.2d:%.2d", localtime->tm_hour, localtime->tm_min); + } + + // extra + if (ImGui::TableNextColumn()) { + renderMessageExtra(msg_reg, e); + } + + ImGui::PopID(); // ent + }); + + // fake empty placeholders + //ImGui::TableNextRow(0, TEXT_BASE_HEIGHT); + //ImGui::TableNextRow(0, TEXT_BASE_HEIGHT); + + ImGui::EndTable(); + } + + if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) { + ImGui::SetScrollHereY(1.f); + } + } + ImGui::EndChild(); + + if (ImGui::BeginChild("text_input", {-150, 0})) { + static bool evil_enter_looses_focus_hack = false; + if (evil_enter_looses_focus_hack) { + ImGui::SetKeyboardFocusHere(); + evil_enter_looses_focus_hack = false; + } + + constexpr ImGuiInputTextFlags input_flags = + ImGuiInputTextFlags_EnterReturnsTrue | + //ImGuiInputTextFlags_AllowTabInput | + ImGuiInputTextFlags_NoHorizontalScroll | + ImGuiInputTextFlags_CtrlEnterForNewLine; + + if (ImGui::InputTextMultiline("##text_input", &text_buffer, {-0.001f, -0.001f}, input_flags)) { + //_mm.sendMessage(*_selected_contact, MessageType::TEXT, text_buffer); + _rmm.sendText(*_selected_contact, text_buffer); + text_buffer.clear(); + evil_enter_looses_focus_hack = true; + } + } + ImGui::EndChild(); + ImGui::SameLine(); + + if (ImGui::BeginChild("buttons")) { + if (ImGui::Button("send\nfile", {-FLT_MIN, 0})) { + _fss.requestFile( + [](const auto& path) -> bool { return std::filesystem::is_regular_file(path); }, + [this](const auto& path){ + _rmm.sendFilePath(*_selected_contact, path.filename().c_str(), path.c_str()); + }, + [](){} + ); + } + } + ImGui::EndChild(); + } + ImGui::EndChild(); + } + } + ImGui::End(); + + _fss.render(); + + //_tc.update(); + //_tc.workLoadQueue(); +} + +// has MessageText +void ChatGui4::renderMessageBodyText(Message3Registry& reg, const Message3 e) { + const auto& msgtext = reg.get(e).text; + + ImVec2 text_size = ImGui::CalcTextSize(msgtext.c_str(), msgtext.c_str()+msgtext.size()); + text_size.x = -FLT_MIN; // fill width (suppresses label) + text_size.y += ImGui::GetStyle().FramePadding.y; // single pad + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, 0}); // make align with text height + ImGui::PushStyleColor(ImGuiCol_FrameBg, {0.f, 0.f, 0.f, 0.f}); // remove text input box + + ImGui::InputTextMultiline( + "", + const_cast(msgtext.c_str()), // ugly const cast + msgtext.size() + 1, // needs to include '\0' + text_size, + ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_NoHorizontalScroll + ); + + ImGui::PopStyleColor(); + ImGui::PopStyleVar(); +} + +void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) { +#if 0 + if (msg_reg.all_of(e)) { + switch (msg_reg.get(e).state) { + case Components::TransferState::State::running: ImGui::TextUnformatted("running"); break; + case Components::TransferState::State::paused: ImGui::TextUnformatted("paused"); break; + case Components::TransferState::State::failed: ImGui::TextUnformatted("failed"); break; + case Components::TransferState::State::finished: ImGui::TextUnformatted("finished"); break; + } + } else { + assert(false); + } +#endif + // TODO: better way to display state + if (reg.all_of(e)) { + ImGui::TextUnformatted("paused"); + } else { + // TODO: missing other staes + ImGui::TextUnformatted("running"); + } + + // if in offered state + // paused, never started + if ( + reg.all_of(e) && + reg.all_of(e) && + // TODO: how does restarting a broken/incomplete transfer look like? + !reg.all_of(e) && + !reg.all_of(e) + ) { + if (ImGui::Button("save to")) { + _fss.requestFile( + [](const auto& path) -> bool { return std::filesystem::is_directory(path); }, + [this, ®, e](const auto& path) { + if (reg.valid(e)) { // still valid + reg.emplace(e, path.string()); + _rmm.throwEventUpdate(reg, e); + } + }, + [](){} + ); + } + } + + // down progress + if (reg.all_of(e)) { + ImGui::TextUnformatted("down"); + if (reg.all_of(e)) { + ImGui::SameLine(); + ImGui::ProgressBar( + float(reg.get(e).total) / reg.get(e).total_size, + {-FLT_MIN, TEXT_BASE_HEIGHT} + ); + // TODO: numbers + } + } + + // (can be both) + // up progess + if (reg.all_of(e)) { + ImGui::TextUnformatted(" up"); + if (reg.all_of(e)) { + ImGui::SameLine(); + ImGui::ProgressBar( + float(reg.get(e).total) / reg.get(e).total_size, + {-FLT_MIN, TEXT_BASE_HEIGHT} + ); + // TODO: numbers + } + } + + const auto file_list = reg.get(e).file_list; + + // if has local, display save base path? + + for (size_t i = 0; i < file_list.size(); i++) { + // TODO: selectable text widget + ImGui::Bullet(); ImGui::Text("%s (%lu)", file_list[i].file_name.c_str(), file_list[i].file_size); + } + +#if 0 + + // TODO: use frame dims + if (file_list.size() == 1 && reg.all_of(e)) { + auto [id, width, height] = _tc.get(reg.get(e).file_list.front()); + + // if cache gives 0s, fall back to frame dims (eg if pic not loaded yet) + if (width > 0 || height > 0) { + const auto& frame_dims = reg.get(e); + width = frame_dims.width; + height = frame_dims.height; + } + + // TODO: config + const auto max_inline_height = 10*TEXT_BASE_HEIGHT; + if (height > max_inline_height) { + const float scale = max_inline_height / height; + height = max_inline_height; + width *= scale; + } + ImGui::Image(id, ImVec2{static_cast(width), static_cast(height)}); + } +#endif +} + +void ChatGui4::renderMessageExtra(Message3Registry& reg, const Message3 e) { + if (reg.all_of(e)) { + ImGui::TextDisabled("fk:%lu", reg.get(e).kind); + } + if (reg.all_of(e)) { + ImGui::TextDisabled("ttf:%u", reg.get(e).transfer_number); + } + + if (reg.all_of(e)) { + ImGui::TextDisabled("msgid:%u", reg.get(e).id); + } + + if (reg.all_of(e)) { + std::string synced_by_text {"syncedBy:"}; + const auto& synced_by = reg.get(e).list; + for (const auto& c : synced_by) { + if (_cr.all_of(c)) { + synced_by_text += "\n sself"; + } else if (_cr.all_of(c)) { + synced_by_text += "\n wself"; + } else { + synced_by_text += "\n >" + (_cr.all_of(c) ? _cr.get(c).name : ""); + } + } + ImGui::TextDisabled("%s", synced_by_text.c_str()); + } +} + +void ChatGui4::renderContactList(void) { + if (ImGui::BeginChild("contacts", {TEXT_BASE_WIDTH*35, 0})) { + //for (const auto& c : _cm.getBigContacts()) { + for (const auto& c : _cr.view()) { + if (renderContactListContactBig(c)) { + _selected_contact = c; + } + } + } + ImGui::EndChild(); +} + +bool ChatGui4::renderContactListContactBig(const Contact3 c) { + // TODO: + // - unread message + // - avatar img + // - connection status + // - user status + // - status message + // - context menu n shit? + + // +------+ + // | | *Name (Alias?) + // |Avatar| Satus Message + // | | user status (online/away/busy)-direct/relayed / offline + // +------+ + + auto label = "###" + std::to_string(entt::to_integral(c)); + + ImVec2 orig_curser_pos = ImGui::GetCursorPos(); + bool selected = ImGui::Selectable(label.c_str(), _selected_contact.has_value() && *_selected_contact == c, 0, {0,3*TEXT_BASE_HEIGHT}); + ImVec2 post_curser_pos = ImGui::GetCursorPos(); + + ImVec2 img_curser { + orig_curser_pos.x + ImGui::GetStyle().FramePadding.x, + orig_curser_pos.y + ImGui::GetStyle().FramePadding.y + }; + + float img_y { + //(post_curser_pos.y - orig_curser_pos.y) - ImGui::GetStyle().FramePadding.y*2 + TEXT_BASE_HEIGHT*3 - ImGui::GetStyle().FramePadding.y*2 + }; + + ImGui::SetCursorPos(img_curser); + const ImVec4 color_online_direct{0.3, 1, 0, 1}; + const ImVec4 color_online_cloud{0, 1, 0.8, 1}; + const ImVec4 color_offline{0.4, 0.4, 0.4, 1}; + + ImVec4 color_current = color_offline; + if (_cr.all_of(c)) { + const auto c_state = _cr.get(c).state; + if (c_state == Contact::Components::ConnectionState::State::direct) { + color_current = color_online_direct; + } else if (c_state == Contact::Components::ConnectionState::State::cloud) { + color_current = color_online_cloud; + } + } + + // avatar +#if 0 + const auto [id, width, height] = _tc.get("test"); + ImGui::Image( + id, + ImVec2{img_y, img_y}, + {0, 0}, + {1, 1}, + {1, 1, 1, 1}, + color_current + ); +#else + ImGui::Dummy({img_y, img_y}); +#endif + + ImGui::SameLine(); + ImGui::BeginGroup(); + { + ImGui::Text("%s", (_cr.all_of(c) ? _cr.get(c).name.c_str() : "")); + //ImGui::Text("status message..."); + //ImGui::TextDisabled("hi"); + //ImGui::RenderTextEllipsis + } + ImGui::EndGroup(); + + ImGui::SetCursorPos(post_curser_pos); + return selected; +} + +bool ChatGui4::renderContactListContactSmall(const Contact3 c) { + std::string label; + + label += (_cr.all_of(c) ? _cr.get(c).name.c_str() : ""); + label += "###"; + label += std::to_string(entt::to_integral(c)); + + return ImGui::Selectable(label.c_str(), _selected_contact.has_value() && *_selected_contact == c); +} + +bool ChatGui4::renderSubContactListContact(const Contact3 c) { + std::string label; + + if (_cr.all_of(c)) { + const auto c_state = _cr.get(c).state; + if (c_state == Contact::Components::ConnectionState::State::direct) { + label += "[D] "; + } else if (c_state == Contact::Components::ConnectionState::State::cloud) { + label += "[C] "; + } else { + label += "[-] "; + } + } else { + label += "[?] "; + } + + label += (_cr.all_of(c) ? _cr.get(c).name.c_str() : ""); + label += "###"; + label += std::to_string(entt::to_integral(c)); + + return ImGui::Selectable(label.c_str(), _selected_contact.has_value() && *_selected_contact == c); +} + diff --git a/src/chat_gui4.hpp b/src/chat_gui4.hpp new file mode 100644 index 00000000..f8097794 --- /dev/null +++ b/src/chat_gui4.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include + +#include "./texture_uploader.hpp" +#include "./file_selector.hpp" + +#include +#include + +class ChatGui4 { + RegistryMessageModel& _rmm; + Contact3Registry& _cr; + + FileSelector _fss; + + std::optional _selected_contact; + + bool _show_chat_extra_info {true}; + + float TEXT_BASE_WIDTH {1}; + float TEXT_BASE_HEIGHT {1}; + + public: + ChatGui4( + RegistryMessageModel& rmm, + Contact3Registry& cr, + TextureUploaderI& tu + ); + + public: + void render(void); + + private: + void renderMessageBodyText(Message3Registry& reg, const Message3 e); + void renderMessageBodyFile(Message3Registry& reg, const Message3 e); + void renderMessageExtra(Message3Registry& reg, const Message3 e); + + void renderContactList(void); + bool renderContactListContactBig(const Contact3 c); + bool renderContactListContactSmall(const Contact3 c); + bool renderSubContactListContact(const Contact3 c); +}; + + diff --git a/src/main_screen.cpp b/src/main_screen.cpp index c4cf0770..b9ade312 100644 --- a/src/main_screen.cpp +++ b/src/main_screen.cpp @@ -13,7 +13,8 @@ MainScreen::MainScreen(SDL_Renderer* renderer_, std::string save_path) : tcm(cr, tc, tc), tmm(rmm, cr, tcm, tc, tc), ttm(rmm, cr, tcm, tc, tc), - sdlrtu(renderer_) + sdlrtu(renderer_), + cg(rmm, cr, sdlrtu) { tel.subscribeAll(tc); @@ -61,6 +62,8 @@ Screen* MainScreen::poll(bool& quit) { pm.tick(time_delta); + cg.render(); + { bool open = !quit; ImGui::ShowDemoWindow(&open); diff --git a/src/main_screen.hpp b/src/main_screen.hpp index 22f8f25b..2dfff163 100644 --- a/src/main_screen.hpp +++ b/src/main_screen.hpp @@ -15,6 +15,7 @@ #include "./tox_client.hpp" #include "./sdlrenderer_texture_uploader.hpp" +#include "./chat_gui4.hpp" #include #include @@ -45,6 +46,7 @@ struct MainScreen final : public Screen { SDLRendererTextureUploader sdlrtu; //OpenGLTextureUploader ogltu; + ChatGui4 cg; MainScreen(SDL_Renderer* renderer_, std::string save_path); ~MainScreen(void);