Compare commits

...

8 Commits

16 changed files with 588 additions and 160 deletions

View File

@ -52,12 +52,14 @@ add_executable(tomato
./sdl_clipboard_utils.hpp ./sdl_clipboard_utils.hpp
./sdl_clipboard_utils.cpp ./sdl_clipboard_utils.cpp
./chat_gui/theme.hpp
./chat_gui/theme.cpp
./chat_gui/contact_list.hpp
./chat_gui/contact_list.cpp
./chat_gui/file_selector.hpp ./chat_gui/file_selector.hpp
./chat_gui/file_selector.cpp ./chat_gui/file_selector.cpp
./chat_gui/send_image_popup.hpp ./chat_gui/send_image_popup.hpp
./chat_gui/send_image_popup.cpp ./chat_gui/send_image_popup.cpp
./chat_gui/settings_window.hpp ./chat_gui/settings_window.hpp
./chat_gui/settings_window.cpp ./chat_gui/settings_window.cpp

View File

@ -0,0 +1,301 @@
#include "./contact_list.hpp"
#include <solanaceae/contact/components.hpp>
#include <imgui/imgui.h>
//#include <imgui/imgui_internal.h>
#include <array>
static void drawIconDirectLines(
const ImVec2 p0,
const ImVec2 p1_o,
const ImU32 col,
const float thickness
) {
#define PLINE(x0, y0, x1, y1) \
ImGui::GetWindowDrawList()->AddLine( \
{p0.x + p1_o.x*(x0), p0.y + p1_o.y*(y0)}, \
{p0.x + p1_o.x*(x1), p0.y + p1_o.y*(y1)}, \
col, \
thickness \
);
// arrow 1
// (3,1) -> (9,7)
PLINE(0.3f, 0.1f, 0.9f, 0.7f)
// (9,7) -> (9,5)
PLINE(0.9f, 0.7f, 0.9f, 0.5f)
// (9,7) -> (7,7)
PLINE(0.9f, 0.7f, 0.7f, 0.7f)
// arrow 2
// (7,9) -> (1,3)
PLINE(0.7f, 0.9f, 0.1f, 0.3f)
// (1,3) -> (3,3)
PLINE(0.1f, 0.3f, 0.3f, 0.3f)
// (1,3) -> (1,5)
PLINE(0.1f, 0.3f, 0.1f, 0.5f)
#undef PLINE
}
static void drawIconDirect(
const ImVec2 p0,
const ImVec2 p1_o,
const ImU32 col_main,
const ImU32 col_back
) {
// dark background
// the circle looks bad in light mode
//ImGui::GetWindowDrawList()->AddCircleFilled({p0.x + p1_o.x*0.5f, p0.y + p1_o.y*0.5f}, p1_o.x*0.5f, col_back);
drawIconDirectLines(p0, p1_o, col_back, 4.0f);
drawIconDirectLines(p0, p1_o, col_main, 1.5f);
}
static void drawIconCloud(
const ImVec2 p0,
const ImVec2 p1_o,
const ImU32 col_main,
const ImU32 col_back
) {
std::array<ImVec2, 19> points {{
{0.2f, 0.9f},
{0.8f, 0.9f},
{0.9f, 0.8f},
{0.9f, 0.7f},
{0.7f, 0.7f},
{0.9f, 0.5f},
{0.9f, 0.4f},
{0.8f, 0.2f},
{0.6f, 0.2f},
{0.5f, 0.3f},
{0.5f, 0.5f},
{0.4f, 0.4f},
{0.3f, 0.4f},
{0.2f, 0.5f},
{0.2f, 0.6f},
{0.3f, 0.7f},
{0.1f, 0.7f},
{0.1f, 0.8f},
{0.2f, 0.9f},
}};
for (auto& v : points) {
v.y -= 0.1f;
v = {p0.x + p1_o.x*v.x, p0.y + p1_o.y*v.y};
}
// the circle looks bad in light mode
//ImGui::GetWindowDrawList()->AddCircleFilled({p0.x + p1_o.x*0.5f, p0.y + p1_o.y*0.5f}, p1_o.x*0.5f, col_back);
ImGui::GetWindowDrawList()->AddPolyline(points.data(), points.size(), col_back, ImDrawFlags_None, 4.f);
ImGui::GetWindowDrawList()->AddPolyline(points.data(), points.size(), col_main, ImDrawFlags_None, 1.5f);
}
void renderAvatar(
const Theme& th,
ContactTextureCache& contact_tc,
const Contact3Handle c,
ImVec2 box
) {
// deploy dummy of same size and check visibility
const auto orig_curser_pos = ImGui::GetCursorPos();
ImGui::Dummy(box);
if (ImGui::IsItemVisible()) {
ImGui::SetCursorPos(orig_curser_pos); // reset for actual img
ImVec4 color_current = th.getColor<ThemeCol_Contact::avatar_offline>();
if (c.all_of<Contact::Components::ConnectionState>()) {
const auto c_state = c.get<Contact::Components::ConnectionState>().state;
if (c_state == Contact::Components::ConnectionState::State::direct) {
color_current = th.getColor<ThemeCol_Contact::avatar_online_direct>();
} else if (c_state == Contact::Components::ConnectionState::State::cloud) {
color_current = th.getColor<ThemeCol_Contact::avatar_online_cloud>();
}
}
// avatar
const auto [id, width, height] = contact_tc.get(c);
ImGui::Image(
id,
box,
{0, 0},
{1, 1},
{1, 1, 1, 1},
color_current
);
}
}
bool renderContactBig(
const Theme& th,
ContactTextureCache& contact_tc,
const Contact3Handle c,
int line_height,
const bool unread,
const bool selectable,
const bool selected
) {
if (line_height < 1) {
line_height = 1;
}
// we dont need ### bc there is no named prefix
auto label = "##" + std::to_string(entt::to_integral(c.entity()));
const bool request_incoming = c.all_of<Contact::Components::RequestIncoming>();
const bool request_outgoing = c.all_of<Contact::Components::TagRequestOutgoing>();
ImVec2 orig_curser_pos = ImGui::GetCursorPos();
// HACK: fake selected to make it draw a box for us
const bool show_selected = request_incoming || request_outgoing || selected;
if (request_incoming) {
ImGui::PushStyleColor(
ImGuiCol_Header,
th.getColor<ThemeCol_Contact::request_incoming>()
);
} else if (request_outgoing) {
ImGui::PushStyleColor(
ImGuiCol_Header,
th.getColor<ThemeCol_Contact::request_outgoing>()
);
}
const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing();
bool got_selected = false;
if (selectable) {
got_selected = ImGui::Selectable(label.c_str(), show_selected, ImGuiSelectableFlags_None, {0, line_height*TEXT_BASE_HEIGHT});
} else {
got_selected = ImGui::InvisibleButton(label.c_str(), {-FLT_MIN, line_height*TEXT_BASE_HEIGHT});
}
if (request_incoming || request_outgoing) {
ImGui::PopStyleColor();
}
const auto* cstate = c.try_get<Contact::Components::ConnectionState>();
if (ImGui::BeginItemTooltip()) {
if (cstate != nullptr) {
ImGui::Text("Connection state: %s",
(cstate->state == Contact::Components::ConnectionState::disconnected)
? "offline"
: (cstate->state == Contact::Components::ConnectionState::direct)
? "online (direct)"
: "online (cloud)"
);
} else {
ImGui::TextUnformatted("Connection state: unknown");
}
// TODO: add a whole bunch more info
ImGui::EndTooltip();
}
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 {TEXT_BASE_HEIGHT*line_height - ImGui::GetStyle().FramePadding.y*2};
ImGui::SetCursorPos(img_curser);
renderAvatar(th, contact_tc, c, {img_y, img_y});
ImGui::SameLine();
ImGui::BeginGroup();
{
{ // line 1
if (line_height == 1 && cstate != nullptr) {
// icon pos
auto p0 = ImGui::GetCursorScreenPos();
p0.y += ImGui::GetStyle().FramePadding.y;
ImVec2 p1_o = {img_y, img_y}; // img_y is 1 line_height in this case
const ImU32 col_back = ImGui::GetColorU32(th.getColor<ThemeCol_Contact::icon_backdrop>());
if (cstate->state == Contact::Components::ConnectionState::direct) { // direct icon
drawIconDirect(
p0,
p1_o,
ImGui::GetColorU32(th.getColor<ThemeCol_Contact::avatar_online_direct>()),
col_back
);
} else if (cstate->state == Contact::Components::ConnectionState::cloud) { // cloud icon
drawIconCloud(
p0,
p1_o,
ImGui::GetColorU32(th.getColor<ThemeCol_Contact::avatar_online_cloud>()),
col_back
);
}
ImGui::Dummy(p1_o);
ImGui::SameLine(0.f, ImGui::GetStyle().ItemSpacing.x*0.5f);
}
ImGui::Text("%s%s", unread?"* ":"", (c.all_of<Contact::Components::Name>() ? c.get<Contact::Components::Name>().name.c_str() : "<unk>"));
}
// line 2
if (line_height >= 2) {
if (request_incoming) {
ImGui::TextUnformatted("Incoming request/invite");
} else if (request_outgoing) {
ImGui::TextUnformatted("Outgoing request/invite");
} else {
if (cstate != nullptr) {
// icon pos
auto p0 = ImGui::GetCursorScreenPos();
p0.y += ImGui::GetStyle().FramePadding.y;
const float box_hight = TEXT_BASE_HEIGHT - ImGui::GetStyle().FramePadding.y*2;
ImVec2 p1_o = {box_hight, box_hight};
const ImU32 col_back = ImGui::GetColorU32(th.getColor<ThemeCol_Contact::icon_backdrop>());
if (cstate->state == Contact::Components::ConnectionState::direct) { // direct icon
drawIconDirect(
p0,
p1_o,
ImGui::GetColorU32(th.getColor<ThemeCol_Contact::avatar_online_direct>()),
col_back
);
} else if (cstate->state == Contact::Components::ConnectionState::cloud) { // cloud icon
drawIconCloud(
p0,
p1_o,
ImGui::GetColorU32(th.getColor<ThemeCol_Contact::avatar_online_cloud>()),
col_back
);
}
ImGui::Dummy(p1_o);
ImGui::SameLine(0.f, ImGui::GetStyle().ItemSpacing.x*0.5f);
}
if (
const auto* slt = c.try_get<Contact::Components::StatusText>();
slt != nullptr &&
!slt->text.empty() &&
slt->first_line_length > 0 &&
slt->first_line_length <= slt->text.size()
) {
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]);
ImGui::TextUnformatted(slt->text.c_str(), slt->text.c_str() + slt->first_line_length);
ImGui::PopStyleColor();
} else {
ImGui::TextDisabled(""); // or dummy?
}
}
// line 3
//if (line_height >= 3) {
// constexpr std::string_view test_text{"text"};
// ImGui::RenderTextEllipsis(ImGui::GetWindowDrawList(), ImVec2{}, ImVec2{}, 1.f, 1.f, test_text.data(), test_text.data()+test_text.size(), nullptr);
//}
}
}
ImGui::EndGroup();
ImGui::SetCursorPos(post_curser_pos);
return got_selected;
}

View File

@ -0,0 +1,43 @@
#pragma once
#include "./texture_cache_defs.hpp"
#include "./theme.hpp"
#include <solanaceae/contact/contact_model3.hpp>
enum class ThemeCol_Contact {
request_incoming,
request_outgoing,
avatar_online_direct,
avatar_online_cloud,
avatar_offline,
icon_backdrop,
};
void renderAvatar(
const Theme& th,
ContactTextureCache& contact_tc,
const Contact3Handle c,
ImVec2 box
);
// returns true if clicked, if selectable, will highlight on hover and respect selected
// TODO: refine
// +------+
// | | *Name (Alias?)
// |Avatar| Satus Message <-- richpresence interface?
// | | user status (online/away/busy)-direct/relayed / offline <-- last text?
// +------+
bool renderContactBig(
const Theme& th,
ContactTextureCache& contact_tc,
const Contact3Handle c,
int line_height = 3,
const bool unread = false,
const bool selectable = false,
const bool selected = false
);

View File

@ -0,0 +1,12 @@
#pragma once
#include <solanaceae/message3/registry_message_model.hpp>
#include "../texture_cache.hpp"
#include "../tox_avatar_loader.hpp"
#include "../message_image_loader.hpp"
using ContactTextureCache = TextureCache<void*, Contact3, ToxAvatarLoader>;
using MessageTextureCache = TextureCache<void*, Message3Handle, MessageImageLoader>;

62
src/chat_gui/theme.cpp Normal file
View File

@ -0,0 +1,62 @@
#include "./theme.hpp"
// HACK: includes everything and sets theme defaults
#include "./contact_list.hpp"
//#include <iostream>
//enum class TestThemeSet {
//Value1,
//};
//// specialization
////template<>
////std::string typeValueName(TestThemeSet v) {
////switch (v) {
////case TestThemeSet::Value1: return "Value1";
////default: return "unk";
////}
////}
Theme::Theme(void) {
load();
}
void Theme::update(void) {
}
bool Theme::load(void) {
name = "Default";
//setColor<TestThemeSet::Value1>(ImVec4{});
//std::cout << "test value name: " << getColorName<TestThemeSet::Value1>() << "\n";
return true;
}
bool Theme::store(void) {
return true;
}
Theme getDefaultThemeDark(void) {
Theme t;
t.setColor<ThemeCol_Contact::request_incoming >({0.98f, 0.41f, 0.26f, 0.52f});
t.setColor<ThemeCol_Contact::request_outgoing >({0.98f, 0.26f, 0.41f, 0.52f});
t.setColor<ThemeCol_Contact::avatar_online_direct >({0.3f, 1.0f, 0.0f, 1.0f});
t.setColor<ThemeCol_Contact::avatar_online_cloud >({0.0f, 1.0f, 0.8f, 1.0f});
t.setColor<ThemeCol_Contact::avatar_offline >({0.4f, 0.4f, 0.4f, 1.0f});
t.setColor<ThemeCol_Contact::icon_backdrop >({0.0f, 0.0f, 0.0f, 0.4f});
return t;
}
Theme getDefaultThemeLight(void) {
// HACK: inherit dark and only diff
Theme t = getDefaultThemeDark();
return t;
}

76
src/chat_gui/theme.hpp Normal file
View File

@ -0,0 +1,76 @@
#pragma once
#include <imgui/imgui.h>
#include <entt/container/dense_map.hpp>
#include <entt/core/type_info.hpp>
#include <string>
#include <type_traits>
// default is resolving to its value
template<typename T>
std::string typeValueName(T V) {
return std::to_string(static_cast<std::underlying_type_t<T>>(V));
}
// stores theming values and colors not expressed by imgui directly
struct Theme {
using key_type = entt::id_type;
entt::dense_map<key_type, ImVec4> colors;
entt::dense_map<key_type, std::string> colors_name;
// TODO: spec out dependencies
// TODO: what for
entt::dense_map<key_type, float> single_values;
std::string name; // theme name
Theme(void);
// call when any color changed, so dependencies can be resolved
void update(void);
template<auto V>
void setColor(ImVec4 color) {
constexpr auto key = entt::type_hash<entt::tag<static_cast<entt::id_type>(V)>>::value();
colors[key] = color;
if (!colors_name.contains(key)) {
std::string key_name = static_cast<std::string>(
entt::type_name<decltype(V)>::value()
) + ":" + typeValueName(V);
colors_name[key] = key_name;
}
}
template<auto V>
ImVec4 getColor(void) const {
constexpr auto key = entt::type_hash<entt::tag<static_cast<entt::id_type>(V)>>::value();
const auto it = colors.find(key);
if (it != colors.end()) {
return it->second;
} else {
return {}; // TODO: pink as default?
}
}
template<auto V>
std::string_view getColorName(void) const {
constexpr auto key = entt::type_hash<entt::tag<static_cast<entt::id_type>(V)>>::value();
if (colors_name.contains(key)) {
return colors_name.at(key);
} else {
return "unk";
}
}
// TODO: actually serialize from config?
bool load(void);
bool store(void);
};
Theme getDefaultThemeDark(void);
Theme getDefaultThemeLight(void);

View File

@ -13,6 +13,8 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include "./chat_gui/contact_list.hpp"
#include "./media_meta_info_loader.hpp" #include "./media_meta_info_loader.hpp"
#include "./sdl_clipboard_utils.hpp" #include "./sdl_clipboard_utils.hpp"
@ -143,8 +145,9 @@ ChatGui4::ChatGui4(
Contact3Registry& cr, Contact3Registry& cr,
TextureUploaderI& tu, TextureUploaderI& tu,
ContactTextureCache& contact_tc, ContactTextureCache& contact_tc,
MessageTextureCache& msg_tc MessageTextureCache& msg_tc,
) : _conf(conf), _rmm(rmm), _cr(cr), _contact_tc(contact_tc), _msg_tc(msg_tc), _sip(tu) { Theme& theme
) : _conf(conf), _rmm(rmm), _cr(cr), _contact_tc(contact_tc), _msg_tc(msg_tc), _theme(theme), _sip(tu) {
} }
ChatGui4::~ChatGui4(void) { ChatGui4::~ChatGui4(void) {
@ -197,15 +200,40 @@ float ChatGui4::render(float time_delta) {
const bool highlight_private {!_cr.all_of<Contact::Components::TagPrivate>(*_selected_contact)}; const bool highlight_private {!_cr.all_of<Contact::Components::TagPrivate>(*_selected_contact)};
if (ImGui::BeginChild(chat_label.c_str(), {0, 0}, true)) { if (ImGui::BeginChild(chat_label.c_str(), {0, 0}, ImGuiChildFlags_Border, ImGuiWindowFlags_MenuBar)) {
if (ImGui::BeginMenuBar()) {
if (ImGui::BeginMenu("debug")) {
ImGui::Checkbox("show extra info", &_show_chat_extra_info);
ImGui::Checkbox("show avatar transfers", &_show_chat_avatar_tf);
ImGui::SeparatorText("tox");
// TODO: cheese it and rename to copy id?
if (_cr.all_of<Contact::Components::ToxGroupPersistent>(*_selected_contact)) {
if (ImGui::MenuItem("copy ngc chatid")) {
const auto& chat_id = _cr.get<Contact::Components::ToxGroupPersistent>(*_selected_contact).chat_id.data;
const auto chat_id_str = bin2hex(std::vector<uint8_t>{chat_id.begin(), chat_id.end()});
ImGui::SetClipboardText(chat_id_str.c_str());
}
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
renderContactBig(_theme, _contact_tc, {_cr, *_selected_contact}, 3, false, false, false);
ImGui::Separator();
if (sub_contacts != nullptr && !_cr.all_of<Contact::Components::TagPrivate>(*_selected_contact) && _cr.all_of<Contact::Components::TagGroup>(*_selected_contact)) { if (sub_contacts != nullptr && !_cr.all_of<Contact::Components::TagPrivate>(*_selected_contact) && _cr.all_of<Contact::Components::TagGroup>(*_selected_contact)) {
if (!sub_contacts->empty()) { if (!sub_contacts->empty()) {
if (ImGui::BeginChild("subcontacts", {150, -100}, true)) { if (ImGui::BeginChild("subcontacts", {175, -100}, true)) {
ImGui::Text("subs: %zu", sub_contacts->size()); ImGui::Text("subs: %zu", sub_contacts->size());
ImGui::Separator(); ImGui::Separator();
for (const auto& c : *sub_contacts) { for (const auto& c : *sub_contacts) {
// TODO: can a sub be selected? no // TODO: can a sub be selected? no
if (renderSubContactListContact(c, _selected_contact.has_value() && *_selected_contact == c)) { //if (renderSubContactListContact(c, _selected_contact.has_value() && *_selected_contact == c)) {
if (renderContactBig(_theme, _contact_tc, {_cr, c}, 1)) {
_text_input_buffer.insert(0, (_cr.all_of<Contact::Components::Name>(c) ? _cr.get<Contact::Components::Name>(c).name : "<unk>") + ": "); _text_input_buffer.insert(0, (_cr.all_of<Contact::Components::Name>(c) ? _cr.get<Contact::Components::Name>(c).name : "<unk>") + ": ");
} }
} }
@ -256,27 +284,11 @@ float ChatGui4::render(float time_delta) {
ImGui::EndChild(); ImGui::EndChild();
} }
if (ImGui::BeginChild("message_log", {0, -100}, false, ImGuiWindowFlags_MenuBar)) { if (ImGui::BeginChild("message_log", {0, -100}, ImGuiChildFlags_None)) {
if (ImGui::BeginMenuBar()) { // TODO: background image?
if (ImGui::BeginMenu("debug")) { //auto p_min = ImGui::GetCursorScreenPos();
ImGui::Checkbox("show extra info", &_show_chat_extra_info); //auto a_max = ImGui::GetContentRegionAvail();
ImGui::Checkbox("show avatar transfers", &_show_chat_avatar_tf); //ImGui::GetWindowDrawList()->AddImage(0, p_min, {p_min.x+a_max.x, p_min.y+a_max.y});
ImGui::SeparatorText("tox");
// TODO: cheese it and rename to copy id?
if (_cr.all_of<Contact::Components::ToxGroupPersistent>(*_selected_contact)) {
if (ImGui::MenuItem("copy ngc chatid")) {
const auto& chat_id = _cr.get<Contact::Components::ToxGroupPersistent>(*_selected_contact).chat_id.data;
const auto chat_id_str = bin2hex(std::vector<uint8_t>{chat_id.begin(), chat_id.end()});
ImGui::SetClipboardText(chat_id_str.c_str());
}
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
auto* msg_reg_ptr = _rmm.get(*_selected_contact); auto* msg_reg_ptr = _rmm.get(*_selected_contact);
@ -314,7 +326,7 @@ float ChatGui4::render(float time_delta) {
msg_reg.view<Components::UnreadFade>().each([&to_remove, time_delta](const Message3 e, Components::UnreadFade& fade) { msg_reg.view<Components::UnreadFade>().each([&to_remove, time_delta](const Message3 e, Components::UnreadFade& fade) {
// TODO: configurable // TODO: configurable
const float fade_duration = 7.5f; const float fade_duration = 7.5f;
fade.fade -= 1.f/fade_duration * std::min<float>(time_delta, 1.f/10.f); // fps but not below 10 for smooth fade fade.fade -= 1.f/fade_duration * std::min<float>(time_delta, 1.f/8.f); // fps but not below 8 for smooth-ish fade
if (fade.fade <= 0.f) { if (fade.fade <= 0.f) {
to_remove.push_back(e); to_remove.push_back(e);
} }
@ -374,6 +386,10 @@ float ChatGui4::render(float time_delta) {
// name // name
if (ImGui::TableNextColumn()) { if (ImGui::TableNextColumn()) {
const float img_y {TEXT_BASE_HEIGHT - ImGui::GetStyle().FramePadding.y*2};
renderAvatar(_theme, _contact_tc, {_cr, c_from.c}, {img_y, img_y});
ImGui::SameLine();
if (_cr.all_of<Contact::Components::Name>(c_from.c)) { if (_cr.all_of<Contact::Components::Name>(c_from.c)) {
ImGui::TextUnformatted(_cr.get<Contact::Components::Name>(c_from.c).name.c_str()); ImGui::TextUnformatted(_cr.get<Contact::Components::Name>(c_from.c).name.c_str());
} else { } else {
@ -1075,7 +1091,18 @@ void ChatGui4::renderContactList(void) {
if (ImGui::BeginChild("contacts", {TEXT_BASE_WIDTH*35, 0})) { if (ImGui::BeginChild("contacts", {TEXT_BASE_WIDTH*35, 0})) {
//for (const auto& c : _cm.getBigContacts()) { //for (const auto& c : _cm.getBigContacts()) {
for (const auto& c : _cr.view<Contact::Components::TagBig>()) { for (const auto& c : _cr.view<Contact::Components::TagBig>()) {
if (renderContactListContactBig(c, _selected_contact.has_value() && *_selected_contact == c)) { 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; _selected_contact = c;
} }
} }
@ -1083,121 +1110,6 @@ void ChatGui4::renderContactList(void) {
ImGui::EndChild(); ImGui::EndChild();
} }
bool ChatGui4::renderContactListContactBig(const Contact3 c, const bool selected) {
// 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));
const bool request_incoming = _cr.all_of<Contact::Components::RequestIncoming>(c);
const bool request_outgoing = _cr.all_of<Contact::Components::TagRequestOutgoing>(c);
ImVec2 orig_curser_pos = ImGui::GetCursorPos();
// HACK: fake selected to make it draw a box for us
const bool show_selected = request_incoming || request_outgoing || selected;
if (request_incoming) {
// TODO: theming
ImGui::PushStyleColor(ImGuiCol_Header, {0.98f, 0.41f, 0.26f, 0.52f});
} else if (request_outgoing) {
// TODO: theming
ImGui::PushStyleColor(ImGuiCol_Header, {0.98f, 0.26f, 0.41f, 0.52f});
}
const bool got_selected = ImGui::Selectable(label.c_str(), show_selected, 0, {0,3*TEXT_BASE_HEIGHT});
if (request_incoming || request_outgoing) {
ImGui::PopStyleColor();
}
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<Contact::Components::ConnectionState>(c)) {
const auto c_state = _cr.get<Contact::Components::ConnectionState>(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
const auto [id, width, height] = _contact_tc.get(c);
ImGui::Image(
id,
ImVec2{img_y, img_y},
{0, 0},
{1, 1},
{1, 1, 1, 1},
color_current
);
// TODO: move this out of chat gui
any_unread = false;
ImGui::SameLine();
ImGui::BeginGroup();
{
// 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()) {
#if 0
assert(unread_storage.size() == 0);
assert(unread_storage.cbegin() == unread_storage.cend());
std::cout << "UNREAD ";
for (const auto e : mm->view<Message::Components::TagUnread>()) {
std::cout << entt::to_integral(e) << " ";
}
std::cout << "\n";
#endif
has_unread = true;
any_unread = true;
}
}
ImGui::Text("%s%s", has_unread?"* ":"", (_cr.all_of<Contact::Components::Name>(c) ? _cr.get<Contact::Components::Name>(c).name.c_str() : "<unk>"));
if (request_incoming) {
ImGui::TextUnformatted("Incoming request/invite");
} else if (request_outgoing) {
ImGui::TextUnformatted("Outgoing request/invite");
}
//ImGui::Text("status message...");
//ImGui::TextDisabled("hi");
//ImGui::RenderTextEllipsis
}
ImGui::EndGroup();
ImGui::SetCursorPos(post_curser_pos);
return got_selected;
}
bool ChatGui4::renderContactListContactSmall(const Contact3 c, const bool selected) const { bool ChatGui4::renderContactListContactSmall(const Contact3 c, const bool selected) const {
std::string label; std::string label;
@ -1208,6 +1120,7 @@ bool ChatGui4::renderContactListContactSmall(const Contact3 c, const bool select
return ImGui::Selectable(label.c_str(), selected); return ImGui::Selectable(label.c_str(), selected);
} }
#if 0
bool ChatGui4::renderSubContactListContact(const Contact3 c, const bool selected) const { bool ChatGui4::renderSubContactListContact(const Contact3 c, const bool selected) const {
std::string label; std::string label;
@ -1230,6 +1143,7 @@ bool ChatGui4::renderSubContactListContact(const Contact3 c, const bool selected
return ImGui::Selectable(label.c_str(), selected); return ImGui::Selectable(label.c_str(), selected);
} }
#endif
void ChatGui4::pasteFile(const char* mime_type) { void ChatGui4::pasteFile(const char* mime_type) {
size_t data_size = 0; size_t data_size = 0;

View File

@ -3,6 +3,8 @@
#include <solanaceae/message3/registry_message_model.hpp> #include <solanaceae/message3/registry_message_model.hpp>
#include <solanaceae/util/config_model.hpp> #include <solanaceae/util/config_model.hpp>
#include "./chat_gui/theme.hpp"
#include "./texture_uploader.hpp" #include "./texture_uploader.hpp"
#include "./texture_cache.hpp" #include "./texture_cache.hpp"
#include "./tox_avatar_loader.hpp" #include "./tox_avatar_loader.hpp"
@ -29,6 +31,8 @@ class ChatGui4 {
ContactTextureCache& _contact_tc; ContactTextureCache& _contact_tc;
MessageTextureCache& _msg_tc; MessageTextureCache& _msg_tc;
Theme& _theme;
FileSelector _fss; FileSelector _fss;
SendImagePopup _sip; SendImagePopup _sip;
@ -57,7 +61,8 @@ class ChatGui4 {
Contact3Registry& cr, Contact3Registry& cr,
TextureUploaderI& tu, TextureUploaderI& tu,
ContactTextureCache& contact_tc, ContactTextureCache& contact_tc,
MessageTextureCache& msg_tc MessageTextureCache& msg_tc,
Theme& theme
); );
~ChatGui4(void); ~ChatGui4(void);
@ -65,8 +70,6 @@ class ChatGui4 {
float render(float time_delta); float render(float time_delta);
public: public:
bool any_unread {false};
void sendFilePath(const char* file_path); void sendFilePath(const char* file_path);
private: private:
@ -75,9 +78,8 @@ class ChatGui4 {
void renderMessageExtra(Message3Registry& reg, const Message3 e); void renderMessageExtra(Message3Registry& reg, const Message3 e);
void renderContactList(void); void renderContactList(void);
bool renderContactListContactBig(const Contact3 c, const bool selected);
bool renderContactListContactSmall(const Contact3 c, const bool selected) const; bool renderContactListContactSmall(const Contact3 c, const bool selected) const;
bool renderSubContactListContact(const Contact3 c, const bool selected) const; //bool renderSubContactListContact(const Contact3 c, const bool selected) const;
void pasteFile(const char* mime_type); void pasteFile(const char* mime_type);
}; };

View File

@ -6,6 +6,7 @@
#include <imgui/backends/imgui_impl_sdlrenderer3.h> #include <imgui/backends/imgui_impl_sdlrenderer3.h>
#include "./theme.hpp" #include "./theme.hpp"
#include "./chat_gui/theme.hpp"
#include "./start_screen.hpp" #include "./start_screen.hpp"
@ -58,11 +59,14 @@ int main(int argc, char** argv) {
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
ImGui::CreateContext(); ImGui::CreateContext();
Theme theme;
if (SDL_GetSystemTheme() == SDL_SYSTEM_THEME_LIGHT) { if (SDL_GetSystemTheme() == SDL_SYSTEM_THEME_LIGHT) {
ImGui::StyleColorsLight(); ImGui::StyleColorsLight();
theme = getDefaultThemeLight();
} else { } else {
//ImGui::StyleColorsDark(); //ImGui::StyleColorsDark();
setThemeGreen(); setThemeGreen();
theme = getDefaultThemeDark();
} }
{ {
@ -85,7 +89,7 @@ int main(int argc, char** argv) {
ImGui_ImplSDL3_InitForSDLRenderer(window.get(), renderer.get()); ImGui_ImplSDL3_InitForSDLRenderer(window.get(), renderer.get());
ImGui_ImplSDLRenderer3_Init(renderer.get()); ImGui_ImplSDLRenderer3_Init(renderer.get());
std::unique_ptr<Screen> screen = std::make_unique<StartScreen>(renderer.get()); std::unique_ptr<Screen> screen = std::make_unique<StartScreen>(renderer.get(), theme);
bool quit = false; bool quit = false;

View File

@ -12,7 +12,7 @@
#include <memory> #include <memory>
#include <cmath> #include <cmath>
MainScreen::MainScreen(SDL_Renderer* renderer_, std::string save_path, std::string save_password, std::string new_username, std::vector<std::string> plugins) : MainScreen::MainScreen(SDL_Renderer* renderer_, Theme& theme_, std::string save_path, std::string save_password, std::string new_username, std::vector<std::string> plugins) :
renderer(renderer_), renderer(renderer_),
rmm(cr), rmm(cr),
msnj{cr, {}, {}}, msnj{cr, {}, {}},
@ -24,6 +24,7 @@ MainScreen::MainScreen(SDL_Renderer* renderer_, std::string save_path, std::stri
tmm(rmm, cr, tcm, tc, tc), tmm(rmm, cr, tcm, tc, tc),
ttm(rmm, cr, tcm, tc, tc), ttm(rmm, cr, tcm, tc, tc),
tffom(cr, rmm, tcm, tc, tc), tffom(cr, rmm, tcm, tc, tc),
theme(theme_),
mmil(rmm), mmil(rmm),
tam(rmm, cr, conf), tam(rmm, cr, conf),
sdlrtu(renderer_), sdlrtu(renderer_),
@ -31,7 +32,7 @@ MainScreen::MainScreen(SDL_Renderer* renderer_, std::string save_path, std::stri
contact_tc(tal, sdlrtu), contact_tc(tal, sdlrtu),
mil(), mil(),
msg_tc(mil, sdlrtu), msg_tc(mil, sdlrtu),
cg(conf, rmm, cr, sdlrtu, contact_tc, msg_tc), cg(conf, rmm, cr, sdlrtu, contact_tc, msg_tc, theme),
sw(conf), sw(conf),
tuiu(tc, conf), tuiu(tc, conf),
tdch(tpi) tdch(tpi)

View File

@ -62,6 +62,8 @@ struct MainScreen final : public Screen {
ToxTransferManager ttm; ToxTransferManager ttm;
ToxFriendFauxOfflineMessaging tffom; ToxFriendFauxOfflineMessaging tffom;
Theme& theme;
MediaMetaInfoLoader mmil; MediaMetaInfoLoader mmil;
ToxAvatarManager tam; ToxAvatarManager tam;
@ -89,7 +91,7 @@ struct MainScreen final : public Screen {
uint64_t _window_hidden_ts {0}; uint64_t _window_hidden_ts {0};
float _time_since_event {0.f}; float _time_since_event {0.f};
MainScreen(SDL_Renderer* renderer_, std::string save_path, std::string save_password, std::string new_username, std::vector<std::string> plugins); MainScreen(SDL_Renderer* renderer_, Theme& theme_, std::string save_path, std::string save_password, std::string new_username, std::vector<std::string> plugins);
~MainScreen(void); ~MainScreen(void);
bool handleEvent(SDL_Event& e) override; bool handleEvent(SDL_Event& e) override;

View File

@ -9,7 +9,7 @@
#include <memory> #include <memory>
#include <filesystem> #include <filesystem>
StartScreen::StartScreen(SDL_Renderer* renderer) : _renderer(renderer) { StartScreen::StartScreen(SDL_Renderer* renderer, Theme& theme) : _renderer(renderer), _theme(theme) {
} }
Screen* StartScreen::render(float, bool&) { Screen* StartScreen::render(float, bool&) {
@ -143,7 +143,7 @@ Screen* StartScreen::render(float, bool&) {
} }
} else { } else {
if (ImGui::Button("load", {60, 25})) { if (ImGui::Button("load", {60, 25})) {
auto new_screen = std::make_unique<MainScreen>(_renderer, _tox_profile_path, _password, _user_name, queued_plugin_paths); auto new_screen = std::make_unique<MainScreen>(_renderer, _theme, _tox_profile_path, _password, _user_name, queued_plugin_paths);
return new_screen.release(); return new_screen.release();
} }
} }

View File

@ -2,6 +2,7 @@
#include "./screen.hpp" #include "./screen.hpp"
#include "./chat_gui/theme.hpp"
#include "./chat_gui/file_selector.hpp" #include "./chat_gui/file_selector.hpp"
#include <vector> #include <vector>
@ -14,6 +15,7 @@ extern "C" {
struct StartScreen final : public Screen { struct StartScreen final : public Screen {
SDL_Renderer* _renderer; SDL_Renderer* _renderer;
Theme& _theme;
FileSelector _fss; FileSelector _fss;
bool _new_save {false}; bool _new_save {false};
@ -26,7 +28,7 @@ struct StartScreen final : public Screen {
std::vector<std::string> queued_plugin_paths; std::vector<std::string> queued_plugin_paths;
StartScreen(void) = delete; StartScreen(void) = delete;
StartScreen(SDL_Renderer* renderer); StartScreen(SDL_Renderer* renderer, Theme& theme);
~StartScreen(void) = default; ~StartScreen(void) = default;
// return nullptr if not next // return nullptr if not next

View File

@ -187,7 +187,8 @@ std::optional<TextureEntry> ToxAvatarLoader::load(TextureUploaderI& tu, Contact3
if (!_cr.any_of< if (!_cr.any_of<
Contact::Components::ToxFriendPersistent, Contact::Components::ToxFriendPersistent,
Contact::Components::ToxGroupPersistent, Contact::Components::ToxGroupPersistent,
Contact::Components::ToxGroupPeerPersistent Contact::Components::ToxGroupPeerPersistent,
Contact::Components::ID
>(c)) { >(c)) {
return std::nullopt; return std::nullopt;
} }
@ -199,6 +200,12 @@ std::optional<TextureEntry> ToxAvatarLoader::load(TextureUploaderI& tu, Contact3
pixels = generateToxIdenticon(_cr.get<Contact::Components::ToxGroupPersistent>(c).chat_id); pixels = generateToxIdenticon(_cr.get<Contact::Components::ToxGroupPersistent>(c).chat_id);
} else if (_cr.all_of<Contact::Components::ToxGroupPeerPersistent>(c)) { } else if (_cr.all_of<Contact::Components::ToxGroupPeerPersistent>(c)) {
pixels = generateToxIdenticon(_cr.get<Contact::Components::ToxGroupPeerPersistent>(c).peer_key); pixels = generateToxIdenticon(_cr.get<Contact::Components::ToxGroupPeerPersistent>(c).peer_key);
} else if (_cr.all_of<Contact::Components::ID>(c)) {
// TODO: should we really use toxidenticons for other protocols?
// (this is required for self)
auto id_copy = _cr.get<Contact::Components::ID>(c).data;
id_copy.resize(32);
pixels = generateToxIdenticon(id_copy);
} }
TextureEntry new_entry; TextureEntry new_entry;