Compare commits

...

14 Commits

Author SHA1 Message Date
3aaa1b0350 try satisfy macintosh 2023-07-29 22:30:36 +02:00
823a4ae189 missing include 2023-07-29 21:51:24 +02:00
40cd04f9dd fix cross platform file paths to string 2023-07-29 21:25:25 +02:00
6a979d31a0 fix missing includes 2023-07-29 20:58:29 +02:00
56f7db9ae6 msg sorting 2023-07-29 20:51:32 +02:00
d5e2dd2e1f add auto dirty (save toxfile) + minor stuff 2023-07-29 20:39:31 +02:00
93e5bb867b invites 2023-07-29 20:07:59 +02:00
4cd295065b add chat gui, probably works 2023-07-28 18:03:45 +02:00
5a9aacc603 add file selector with sorting 2023-07-27 19:34:47 +02:00
082c4febdf more setup 2023-07-26 20:09:57 +02:00
a848a01527 add tox client and more setup 2023-07-26 12:55:50 +02:00
3a1c15f313 screen concept 2023-07-26 12:24:18 +02:00
e92c7cbfa0 add exe icon 2023-07-26 02:02:06 +02:00
4d09e1fd4a gh windows cd 2023-07-26 01:13:54 +02:00
23 changed files with 1533 additions and 5 deletions

43
.github/workflows/cd.yml vendored Normal file
View File

@ -0,0 +1,43 @@
name: ContinuousDelivery
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
env:
BUILD_TYPE: Release
jobs:
windows:
timeout-minutes: 15
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Install Dependencies
run: vcpkg install libsodium:x64-windows-static pthreads:x64-windows-static
# setup vs env
- uses: ilammy/msvc-dev-cmd@v1
with:
arch: amd64
- name: Configure CMake
run: cmake -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 4
- uses: actions/upload-artifact@v3
with:
name: windows_msvc_x86-64
# TODO: do propper packing
path: |
${{github.workspace}}/build/bin/

BIN
res/icon/tomato_v1_128.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -2,6 +2,28 @@ cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
add_executable(tomato
main.cpp
icon.rc
screen.hpp
start_screen.hpp
start_screen.cpp
main_screen.hpp
main_screen.cpp
./tox_client.hpp
./tox_client.cpp
./auto_dirty.hpp
./auto_dirty.cpp
theme.hpp
texture_uploader.hpp
./sdlrenderer_texture_uploader.hpp
./sdlrenderer_texture_uploader.cpp
./file_selector.hpp
./file_selector.cpp
./chat_gui4.hpp
./chat_gui4.cpp
)
target_compile_features(tomato PUBLIC cxx_std_17)
@ -10,7 +32,7 @@ target_link_libraries(tomato PUBLIC
solanaceae_contact
solanaceae_message3
#solanaceae_plugin
solanaceae_plugin
solanaceae_toxcore
solanaceae_tox_contacts

62
src/auto_dirty.cpp Normal file
View File

@ -0,0 +1,62 @@
#include "./auto_dirty.hpp"
#include "./tox_client.hpp"
// TODO: add more events
void AutoDirty::subscribe(void) {
_tc.subscribe(this, Tox_Event::TOX_EVENT_SELF_CONNECTION_STATUS);
_tc.subscribe(this, Tox_Event::TOX_EVENT_FRIEND_CONNECTION_STATUS);
_tc.subscribe(this, Tox_Event::TOX_EVENT_FRIEND_REQUEST);
_tc.subscribe(this, Tox_Event::TOX_EVENT_GROUP_INVITE);
_tc.subscribe(this, Tox_Event::TOX_EVENT_GROUP_SELF_JOIN);
_tc.subscribe(this, Tox_Event::TOX_EVENT_GROUP_PEER_JOIN);
_tc.subscribe(this, Tox_Event::TOX_EVENT_GROUP_PEER_EXIT);
_tc.subscribe(this, Tox_Event::TOX_EVENT_CONFERENCE_INVITE);
}
AutoDirty::AutoDirty(ToxClient& tc) : _tc(tc) {
subscribe();
}
bool AutoDirty::onToxEvent(const Tox_Event_Self_Connection_Status*) {
_tc.setDirty();
return false;
}
bool AutoDirty::onToxEvent(const Tox_Event_Friend_Connection_Status*) {
_tc.setDirty();
return false;
}
bool AutoDirty::onToxEvent(const Tox_Event_Friend_Request*) {
_tc.setDirty();
return false;
}
bool AutoDirty::onToxEvent(const Tox_Event_Group_Invite*) {
_tc.setDirty();
return false;
}
bool AutoDirty::onToxEvent(const Tox_Event_Group_Self_Join*) {
_tc.setDirty();
return false;
}
bool AutoDirty::onToxEvent(const Tox_Event_Group_Peer_Join*) {
_tc.setDirty();
return false;
}
bool AutoDirty::onToxEvent(const Tox_Event_Group_Peer_Exit*) {
_tc.setDirty();
return false;
}
bool AutoDirty::onToxEvent(const Tox_Event_Conference_Invite*) {
_tc.setDirty();
return false;
}

27
src/auto_dirty.hpp Normal file
View File

@ -0,0 +1,27 @@
#pragma once
#include <solanaceae/toxcore/tox_event_interface.hpp>
// fwd
class ToxClient;
// sets ToxClient dirty on some events
class AutoDirty : public ToxEventI {
ToxClient& _tc;
void subscribe(void); // private
public:
AutoDirty(ToxClient& tc);
protected: // tox events
bool onToxEvent(const Tox_Event_Self_Connection_Status* e) override;
bool onToxEvent(const Tox_Event_Friend_Connection_Status* e) override;
bool onToxEvent(const Tox_Event_Friend_Request* e) override;
bool onToxEvent(const Tox_Event_Group_Invite* e) override;
bool onToxEvent(const Tox_Event_Group_Self_Join* e) override;
bool onToxEvent(const Tox_Event_Group_Peer_Join* e) override;
bool onToxEvent(const Tox_Event_Group_Peer_Exit* e) override;
bool onToxEvent(const Tox_Event_Conference_Invite* e) override;
};

569
src/chat_gui4.cpp Normal file
View File

@ -0,0 +1,569 @@
#include "./chat_gui4.hpp"
#include "./file_selector.hpp"
#include <solanaceae/message3/components.hpp>
#include <solanaceae/tox_messages/components.hpp>
#include <solanaceae/contact/components.hpp>
//#include "./media_meta_info_loader.hpp"
#include <imgui/imgui.h>
#include <imgui/misc/cpp/imgui_stdlib.h>
#include <string>
#include <variant>
#include <vector>
#include <chrono>
#include <filesystem>
#include <ctime>
ChatGui4::ChatGui4(
ConfigModelI& conf,
RegistryMessageModel& rmm,
Contact3Registry& cr,
TextureUploaderI& tu
) : _conf(conf), _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<Contact::Components::ParentOf>(*_selected_contact)) {
const auto sub_contacts = _cr.get<Contact::Components::ParentOf>(*_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<Contact::Components::Name>(c) ? _cr.get<Contact::Components::Name>(c).name : "<unk>") + ": ");
}
}
}
ImGui::EndChild();
ImGui::SameLine();
}
}
const bool request_incoming = _cr.all_of<Contact::Components::RequestIncoming>(*_selected_contact);
const bool request_outgoing = _cr.all_of<Contact::Components::TagRequestOutgoing>(*_selected_contact);
if (request_incoming || request_outgoing) {
// TODO: theming
ImGui::PushStyleColor(ImGuiCol_ChildBg, {0.90f, 0.70f, 0.00f, 0.32f});
if (ImGui::BeginChild("request", {0, TEXT_BASE_HEIGHT*6.1f}, true, ImGuiWindowFlags_NoScrollbar)) {
if (request_incoming) {
const auto& ri = _cr.get<Contact::Components::RequestIncoming>(*_selected_contact);
ImGui::TextUnformatted("You got a request to add this contact.");
static std::string self_name = _conf.get_string("tox", "name").value_or("default_tomato");
if (ri.name) {
ImGui::InputText("name to join with", &self_name);
} else {
//ImGui::TextUnformatted("");
ImGui::Dummy({0, TEXT_BASE_HEIGHT});
}
static std::string password;
if (ri.password) {
ImGui::InputText("password to join with", &password);
} else {
////ImGui::TextUnformatted("");
ImGui::Dummy({0, TEXT_BASE_HEIGHT});
}
if (ImGui::Button("Accept")) {
_cr.get<Contact::Components::ContactModel>(*_selected_contact)->acceptRequest(*_selected_contact, self_name, password);
password.clear();
}
ImGui::SameLine();
if (ImGui::Button("Decline")) {
}
} else {
ImGui::TextUnformatted("You sent a reqeust to add this contact.");
}
}
ImGui::PopStyleColor();
ImGui::EndChild();
}
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<Message::Components::ContactFrom, Message::Components::ContactTo, Message::Components::Timestamp>()
.use<Message::Components::Timestamp>()
.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<Contact::Components::Name>(c_from.c)) {
ImGui::TextUnformatted(_cr.get<Contact::Components::Name>(c_from.c).name.c_str());
} else {
ImGui::TextUnformatted("<unk>");
}
// highlight self
if (_cr.any_of<Contact::Components::TagSelfWeak, Contact::Components::TagSelfStrong>(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<Contact::Components::TagSelfWeak, Contact::Components::TagSelfStrong>(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<Message::Components::MessageText>(e)) {
renderMessageBodyText(msg_reg, e);
} else if (msg_reg.any_of<Message::Components::Transfer::FileInfo>(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::system_clock, std::chrono::milliseconds>{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().u8string(), path.u8string());
},
[](){}
);
}
}
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<Message::Components::MessageText>(e).text;
// TODO: set word wrap
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<char*>(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<Components::TransferState>(e)) {
switch (msg_reg.get<Components::TransferState>(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<Message::Components::Transfer::TagPaused>(e)) {
ImGui::TextUnformatted("paused");
} else {
// TODO: missing other staes
ImGui::TextUnformatted("running");
}
// if in offered state
// paused, never started
if (
reg.all_of<Message::Components::Transfer::TagReceiving>(e) &&
reg.all_of<Message::Components::Transfer::TagPaused>(e) &&
// TODO: how does restarting a broken/incomplete transfer look like?
!reg.all_of<Message::Components::Transfer::FileInfoLocal>(e) &&
!reg.all_of<Message::Components::Transfer::ActionAccept>(e)
) {
if (ImGui::Button("save to")) {
_fss.requestFile(
[](const auto& path) -> bool { return std::filesystem::is_directory(path); },
[this, &reg, e](const auto& path) {
if (reg.valid(e)) { // still valid
reg.emplace<Message::Components::Transfer::ActionAccept>(e, path.string());
_rmm.throwEventUpdate(reg, e);
}
},
[](){}
);
}
}
// down progress
if (reg.all_of<Message::Components::Transfer::TagReceiving>(e)) {
ImGui::TextUnformatted("down");
if (reg.all_of<Message::Components::Transfer::BytesReceived>(e)) {
ImGui::SameLine();
ImGui::ProgressBar(
float(reg.get<Message::Components::Transfer::BytesReceived>(e).total) / reg.get<Message::Components::Transfer::FileInfo>(e).total_size,
{-FLT_MIN, TEXT_BASE_HEIGHT}
);
// TODO: numbers
}
}
// (can be both)
// up progess
if (reg.all_of<Message::Components::Transfer::TagSending>(e)) {
ImGui::TextUnformatted(" up");
if (reg.all_of<Message::Components::Transfer::BytesSent>(e)) {
ImGui::SameLine();
ImGui::ProgressBar(
float(reg.get<Message::Components::Transfer::BytesSent>(e).total) / reg.get<Message::Components::Transfer::FileInfo>(e).total_size,
{-FLT_MIN, TEXT_BASE_HEIGHT}
);
// TODO: numbers
}
}
const auto file_list = reg.get<Message::Components::Transfer::FileInfo>(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<Message::Components::Transfer::FileInfoLocal, Message::Components::FrameDims>(e)) {
auto [id, width, height] = _tc.get(reg.get<Message::Components::Transfer::FileInfoLocal>(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<Message::Components::FrameDims>(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<float>(width), static_cast<float>(height)});
}
#endif
}
void ChatGui4::renderMessageExtra(Message3Registry& reg, const Message3 e) {
if (reg.all_of<Message::Components::Transfer::FileKind>(e)) {
ImGui::TextDisabled("fk:%lu", reg.get<Message::Components::Transfer::FileKind>(e).kind);
}
if (reg.all_of<Message::Components::Transfer::ToxTransferFriend>(e)) {
ImGui::TextDisabled("ttf:%u", reg.get<Message::Components::Transfer::ToxTransferFriend>(e).transfer_number);
}
if (reg.all_of<Message::Components::ToxGroupMessageID>(e)) {
ImGui::TextDisabled("msgid:%u", reg.get<Message::Components::ToxGroupMessageID>(e).id);
}
if (reg.all_of<Message::Components::SyncedBy>(e)) {
std::string synced_by_text {"syncedBy:"};
const auto& synced_by = reg.get<Message::Components::SyncedBy>(e).list;
for (const auto& c : synced_by) {
if (_cr.all_of<Contact::Components::TagSelfStrong>(c)) {
synced_by_text += "\n sself";
} else if (_cr.all_of<Contact::Components::TagSelfWeak>(c)) {
synced_by_text += "\n wself";
} else {
synced_by_text += "\n >" + (_cr.all_of<Contact::Components::Name>(c) ? _cr.get<Contact::Components::Name>(c).name : "<unk>");
}
}
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<Contact::Components::TagBig>()) {
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));
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_contact.has_value() && *_selected_contact == c);
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 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
#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<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 selected;
}
bool ChatGui4::renderContactListContactSmall(const Contact3 c) {
std::string label;
label += (_cr.all_of<Contact::Components::Name>(c) ? _cr.get<Contact::Components::Name>(c).name.c_str() : "<unk>");
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<Contact::Components::ConnectionState>(c)) {
const auto c_state = _cr.get<Contact::Components::ConnectionState>(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<Contact::Components::Name>(c) ? _cr.get<Contact::Components::Name>(c).name.c_str() : "<unk>");
label += "###";
label += std::to_string(entt::to_integral(c));
return ImGui::Selectable(label.c_str(), _selected_contact.has_value() && *_selected_contact == c);
}

48
src/chat_gui4.hpp Normal file
View File

@ -0,0 +1,48 @@
#pragma once
#include <solanaceae/message3/registry_message_model.hpp>
#include <solanaceae/util/config_model.hpp>
#include "./texture_uploader.hpp"
#include "./file_selector.hpp"
#include <vector>
#include <set>
class ChatGui4 {
ConfigModelI& _conf;
RegistryMessageModel& _rmm;
Contact3Registry& _cr;
FileSelector _fss;
std::optional<Contact3> _selected_contact;
bool _show_chat_extra_info {true};
float TEXT_BASE_WIDTH {1};
float TEXT_BASE_HEIGHT {1};
public:
ChatGui4(
ConfigModelI& conf,
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);
};

248
src/file_selector.cpp Normal file
View File

@ -0,0 +1,248 @@
#include "./file_selector.hpp"
#include <imgui/imgui.h>
#include <chrono>
#include <string>
#include <iostream>
#include <vector>
void FileSelector::reset(void) {
_is_valid = [](auto){ return true; };
_on_choose = [](auto){};
_on_cancel = [](){};
}
FileSelector::FileSelector(void) {
reset();
}
void FileSelector::requestFile(
std::function<bool(const std::filesystem::path& path)>&& is_valid,
std::function<void(const std::filesystem::path& path)>&& on_choose,
std::function<void(void)>&& on_cancel
) {
_open_popup = true;
_is_valid = std::move(is_valid);
_on_choose = std::move(on_choose);
_on_cancel = std::move(on_cancel);
}
void FileSelector::render(void) {
if (_open_popup) {
_open_popup = false;
ImGui::OpenPopup("file picker##FileSelector");
const auto TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x;
const auto TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing();
ImGui::SetNextWindowSize({TEXT_BASE_WIDTH*100, TEXT_BASE_HEIGHT*30});
}
if (ImGui::BeginPopupModal("file picker##FileSelector", nullptr/*, ImGuiWindowFlags_NoDecoration*/)) {
const auto TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x;
const auto TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing();
std::filesystem::path current_path = _current_file_path;
current_path.remove_filename();
ImGui::Text("path: %s", _current_file_path.u8string().c_str());
// begin table with selectables
constexpr ImGuiTableFlags table_flags =
ImGuiTableFlags_BordersInnerV |
ImGuiTableFlags_RowBg |
ImGuiTableFlags_SizingFixedFit |
ImGuiTableFlags_ScrollY |
ImGuiTableFlags_Sortable
;
if (ImGui::BeginTable("dir listing", 4, table_flags, {0, -TEXT_BASE_HEIGHT * 2.5f})) {
enum class SortID : ImGuiID {
name = 1,
size,
date
};
ImGui::TableSetupColumn("type", 0, TEXT_BASE_WIDTH);
ImGui::TableSetupColumn("name", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_DefaultSort, 0.f, static_cast<ImGuiID>(SortID::name));
ImGui::TableSetupColumn("size", 0, 0.f, static_cast<ImGuiID>(SortID::size));
ImGui::TableSetupColumn("dd.mm.yyyy - hh:mm", 0, 0.f, static_cast<ImGuiID>(SortID::date));
ImGui::TableSetupScrollFreeze(0, 2);
ImGui::TableHeadersRow();
//ImGui::TableNextRow(0, TEXT_BASE_HEIGHT);
if (current_path.has_parent_path()) {
if (ImGui::TableNextColumn()) {
if (ImGui::Selectable("D##..", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap)) {
_current_file_path = _current_file_path.parent_path();
}
}
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted("..");
}
if (ImGui::TableNextColumn()) {
ImGui::TextDisabled("---");
}
if (ImGui::TableNextColumn()) {
ImGui::TextDisabled("---");
}
}
size_t tmp_id = 0;
// dirs
static const ImU32 dir_bg0_color = ImGui::GetColorU32(ImVec4(0.6, 0.6, 0.1, 0.15));
static const ImU32 dir_bg1_color = ImGui::GetColorU32(ImVec4(0.7, 0.7, 0.2, 0.15));
std::vector<std::filesystem::directory_entry> dirs;
std::vector<std::filesystem::directory_entry> files;
for (auto const& dir_entry : std::filesystem::directory_iterator(current_path)) {
if (dir_entry.is_directory()) {
dirs.push_back(dir_entry);
} else if (dir_entry.is_regular_file()) {
files.push_back(dir_entry);
}
}
// do sorting here
// TODO: cache the result (lol)
if (ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs(); sorts_specs != nullptr && sorts_specs->SpecsCount >= 1) {
switch (static_cast<SortID>(sorts_specs->Specs->ColumnUserID)) {
break; case SortID::name:
if (sorts_specs->Specs->SortDirection == ImGuiSortDirection_Descending) {
std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) -> bool {
return a.path() < b.path();
});
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
return a.path().filename() < b.path().filename();
});
} else {
std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) -> bool {
return a.path() > b.path();
});
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
return a.path().filename() > b.path().filename();
});
}
break; case SortID::size:
if (sorts_specs->Specs->SortDirection == ImGuiSortDirection_Descending) {
// TODO: sort dirs?
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
return a.file_size() < b.file_size();
});
} else {
// TODO: sort dirs?
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
return a.file_size() > b.file_size();
});
}
break; case SortID::date:
if (sorts_specs->Specs->SortDirection == ImGuiSortDirection_Descending) {
std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) -> bool {
return a.last_write_time() < b.last_write_time();
});
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
return a.last_write_time() < b.last_write_time();
});
} else {
std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) -> bool {
return a.last_write_time() > b.last_write_time();
});
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
return a.last_write_time() > b.last_write_time();
});
}
break; default: ;
}
}
for (auto const& dir_entry : dirs) {
if (ImGui::TableNextColumn()) {
if (tmp_id & 0x01) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, dir_bg0_color);
} else {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, dir_bg1_color);
}
ImGui::PushID(tmp_id++);
if (ImGui::Selectable("D", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap)) {
_current_file_path = dir_entry.path() / "";
}
ImGui::PopID();
}
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted((dir_entry.path().filename().u8string() + "/").c_str());
}
if (ImGui::TableNextColumn()) {
ImGui::TextDisabled("---");
}
if (ImGui::TableNextColumn()) {
const auto file_time_converted = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
dir_entry.last_write_time()
- decltype(dir_entry.last_write_time())::clock::now()
+ std::chrono::system_clock::now()
);
const auto ctime = std::chrono::system_clock::to_time_t(file_time_converted);
const auto ltime = std::localtime(&ctime);
ImGui::TextDisabled("%2d.%2d.%2d - %2d:%2d", ltime->tm_mday, ltime->tm_mon, ltime->tm_year + 1900, ltime->tm_hour, ltime->tm_min);
}
}
// files
for (auto const& dir_entry : files) {
if (ImGui::TableNextColumn()) {
ImGui::PushID(tmp_id++);
if (ImGui::Selectable("F", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap)) {
_current_file_path = dir_entry.path();
}
ImGui::PopID();
}
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted(dir_entry.path().filename().u8string().c_str());
}
if (ImGui::TableNextColumn()) {
ImGui::TextDisabled("%s", std::to_string(dir_entry.file_size()).c_str());
}
if (ImGui::TableNextColumn()) {
const auto file_time_converted = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
dir_entry.last_write_time()
- decltype(dir_entry.last_write_time())::clock::now()
+ std::chrono::system_clock::now()
);
const auto ctime = std::chrono::system_clock::to_time_t(file_time_converted);
const auto ltime = std::localtime(&ctime);
ImGui::TextDisabled("%2d.%2d.%2d - %2d:%2d", ltime->tm_mday, ltime->tm_mon, ltime->tm_year + 1900, ltime->tm_hour, ltime->tm_min);
}
}
ImGui::EndTable();
}
if (ImGui::Button("X cancel", {ImGui::GetWindowContentRegionWidth()/2.f, TEXT_BASE_HEIGHT*2})) {
ImGui::CloseCurrentPopup();
_on_cancel();
reset();
}
ImGui::SameLine();
if (ImGui::Button("choose ->", {-FLT_MIN, TEXT_BASE_HEIGHT*2})) {
if (_is_valid(_current_file_path)) {
ImGui::CloseCurrentPopup();
_on_choose(_current_file_path);
reset();
}
}
ImGui::EndPopup();
}
}

30
src/file_selector.hpp Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <filesystem>
#include <functional>
struct FileSelector {
std::filesystem::path _current_file_path = std::filesystem::canonical(".") / ""; // add /
bool _open_popup {false};
std::function<bool(const std::filesystem::path& path)> _is_valid = [](auto){ return true; };
std::function<void(const std::filesystem::path& path)> _on_choose = [](auto){};
std::function<void(void)> _on_cancel = [](){};
void reset(void);
public:
FileSelector(void);
// TODO: supply hints
void requestFile(
std::function<bool(const std::filesystem::path& path)>&& is_valid,
std::function<void(const std::filesystem::path& path)>&& on_choose,
std::function<void(void)>&& on_cancel
);
// call this each frame
void render(void);
};

1
src/icon.rc Normal file
View File

@ -0,0 +1 @@
1 ICON "../res/icon/tomato_v1_128.ico"

View File

@ -6,6 +6,9 @@
#include <imgui/backends/imgui_impl_sdlrenderer3.h>
#include "./theme.hpp"
#include "./sdlrenderer_texture_uploader.hpp"
#include "./start_screen.hpp"
#include <memory>
#include <future>
@ -31,7 +34,7 @@ int main(int argc, char** argv) {
}
std::unique_ptr<SDL_Renderer, decltype(&SDL_DestroyRenderer)> renderer {
SDL_CreateRenderer(window.get(), nullptr, 0),
SDL_CreateRenderer(window.get(), nullptr, SDL_RENDERER_PRESENTVSYNC),
&SDL_DestroyRenderer
};
if (!renderer) {
@ -50,6 +53,8 @@ int main(int argc, char** argv) {
ImGui_ImplSDLRenderer3_Init(renderer.get());
auto imgui_sdlrenderer_scope = std::async(std::launch::deferred, &ImGui_ImplSDLRenderer3_Shutdown);
std::unique_ptr<Screen> screen = std::make_unique<StartScreen>(renderer.get());
bool quit = false;
while (!quit) {
SDL_Event event;
@ -68,7 +73,12 @@ int main(int argc, char** argv) {
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
ImGui::ShowDemoWindow();
//ImGui::ShowDemoWindow();
Screen* ret_screen = screen->poll(quit);
if (ret_screen != nullptr) {
screen.reset(ret_screen);
}
ImGui::Render();
ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData());

108
src/main_screen.cpp Normal file
View File

@ -0,0 +1,108 @@
#include "./main_screen.hpp"
#include <imgui/imgui.h>
#include <SDL3/SDL.h>
#include <memory>
MainScreen::MainScreen(SDL_Renderer* renderer_, std::string save_path) :
renderer(renderer_),
rmm(cr),
mts(rmm),
tc(save_path),
ad(tc),
tcm(cr, tc, tc),
tmm(rmm, cr, tcm, tc, tc),
ttm(rmm, cr, tcm, tc, tc),
sdlrtu(renderer_),
cg(conf, rmm, cr, sdlrtu)
{
tel.subscribeAll(tc);
conf.set("tox", "save_file_path", save_path);
{ // name stuff
auto name = tc.toxSelfGetName();
if (name.empty()) {
name = "tomato";
}
conf.set("tox", "name", name);
tc.setSelfName(name); // TODO: this is ugly
}
// TODO: remove
std::cout << "own address: " << tc.toxSelfGetAddressStr() << "\n";
{ // setup plugin instances
g_provideInstance<ConfigModelI>("ConfigModelI", "host", &conf);
g_provideInstance<Contact3Registry>("Contact3Registry", "host", &cr);
g_provideInstance<RegistryMessageModel>("RegistryMessageModel", "host", &rmm);
g_provideInstance<ToxI>("ToxI", "host", &tc);
g_provideInstance<ToxEventProviderI>("ToxEventProviderI", "host", &tc);
// TODO: pm?
// graphics
g_provideInstance("ImGuiContext", "host", ImGui::GetCurrentContext());
g_provideInstance<TextureUploaderI>("TextureUploaderI", "host", &sdlrtu);
}
conf.dump();
}
MainScreen::~MainScreen(void) {
}
Screen* MainScreen::poll(bool& quit) {
auto new_time = std::chrono::high_resolution_clock::now();
const float time_delta {std::chrono::duration<float, std::chrono::seconds::period>(new_time - last_time).count()};
last_time = new_time;
quit = !tc.iterate();
pm.tick(time_delta);
mts.iterate();
cg.render();
{
bool open = !quit;
ImGui::ShowDemoWindow(&open);
quit = !open;
}
#if 0
{ // texture tests
const size_t width = 8;
const size_t height = 8;
#define W 0xff, 0xff, 0xff, 0xff
#define B 0x00, 0x00, 0x00, 0xff
#define P 0xff, 0x00, 0xff, 0xff
static uint8_t raw_pixel[width*height*4] {
P, W, W, W, W, W, W, P,
W, W, W, W, W, W, W, W,
W, W, W, W, W, W, W, W,
W, W, W, B, B, W, W, W,
W, W, W, B, B, W, W, W,
W, W, W, W, W, W, W, W,
W, W, W, W, W, W, W, W,
P, W, W, W, W, W, W, P,
};
static uint64_t texture = sdlrtu.uploadRGBA(raw_pixel, width, height);
if (ImGui::Begin("test texture")) {
ImGui::Text("test texture windoajsdf");
ImGui::Image(reinterpret_cast<void*>(texture), {width*10, height*10});
}
ImGui::End();
}
#endif
return nullptr;
}

62
src/main_screen.hpp Normal file
View File

@ -0,0 +1,62 @@
#pragma once
#include "./screen.hpp"
#include <solanaceae/util/simple_config_model.hpp>
#include <solanaceae/contact/contact_model3.hpp>
#include <solanaceae/message3/registry_message_model.hpp>
#include <solanaceae/message3/message_time_sort.hpp>
#include <solanaceae/plugin/plugin_manager.hpp>
#include <solanaceae/toxcore/tox_event_logger.hpp>
#include <solanaceae/tox_contacts/tox_contact_model2.hpp>
#include <solanaceae/tox_messages/tox_message_manager.hpp>
#include <solanaceae/tox_messages/tox_transfer_manager.hpp>
#include "./tox_client.hpp"
#include "./auto_dirty.hpp"
#include "./sdlrenderer_texture_uploader.hpp"
#include "./chat_gui4.hpp"
#include <string>
#include <iostream>
#include <chrono>
// fwd
extern "C" {
struct SDL_Renderer;
} // C
struct MainScreen final : public Screen {
SDL_Renderer* renderer;
std::chrono::high_resolution_clock::time_point last_time = std::chrono::high_resolution_clock::now();
SimpleConfigModel conf;
Contact3Registry cr;
RegistryMessageModel rmm;
MessageTimeSort mts;
PluginManager pm;
ToxEventLogger tel{std::cout};
ToxClient tc;
AutoDirty ad;
ToxContactModel2 tcm;
ToxMessageManager tmm;
ToxTransferManager ttm;
SDLRendererTextureUploader sdlrtu;
//OpenGLTextureUploader ogltu;
ChatGui4 cg;
MainScreen(SDL_Renderer* renderer_, std::string save_path);
~MainScreen(void);
// return nullptr if not next
// sets bool quit to true if exit
Screen* poll(bool&) override;
};

10
src/screen.hpp Normal file
View File

@ -0,0 +1,10 @@
#pragma once
struct Screen {
virtual ~Screen(void) = default;
// return nullptr if not next
// sets bool quit to true if exit
virtual Screen* poll(bool& quit) = 0;
};

View File

@ -0,0 +1,32 @@
#include "./sdlrenderer_texture_uploader.hpp"
#include <cassert>
SDLRendererTextureUploader::SDLRendererTextureUploader(SDL_Renderer* renderer_) :
renderer(renderer_)
{
}
uint64_t SDLRendererTextureUploader::uploadRGBA(const uint8_t* data, uint32_t width, uint32_t height) {
// TODO: test if pitch is 4 or 4*width
SDL_Surface* surf = SDL_CreateSurfaceFrom(
(void*)data,
width, height,
4*width,
//SDL_PIXELFORMAT_RGBA8888
SDL_PIXELFORMAT_ABGR8888 // little endian
);
assert(surf); // TODO: add error reporting
SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf);
assert(tex); // TODO: add error reporting
SDL_DestroySurface(surf);
return reinterpret_cast<uint64_t>(tex);
}
void SDLRendererTextureUploader::destroy(uint64_t tex_id) {
SDL_DestroyTexture(static_cast<SDL_Texture*>(reinterpret_cast<void*>(tex_id)));
}

View File

@ -0,0 +1,16 @@
#pragma once
#include "./texture_uploader.hpp"
#include <SDL3/SDL.h>
struct SDLRendererTextureUploader : public TextureUploaderI {
SDL_Renderer* renderer;
SDLRendererTextureUploader(void) = delete;
SDLRendererTextureUploader(SDL_Renderer* renderer_);
~SDLRendererTextureUploader(void) = default;
uint64_t uploadRGBA(const uint8_t* data, uint32_t width, uint32_t height) override;
void destroy(uint64_t tex_id) override;
};

24
src/start_screen.cpp Normal file
View File

@ -0,0 +1,24 @@
#include "./start_screen.hpp"
#include "./main_screen.hpp"
#include <memory>
StartScreen::StartScreen(SDL_Renderer* renderer) : _renderer(renderer) {
}
Screen* StartScreen::poll(bool&) {
// TODO: imgui tox profile selector?
// +----------------------------
// | |*tox profile*| plugins |
// | +------+ +--------
// | | ICON | | fileselector/dropdown?
// | | | | password input
// | +------+ +--------
// +----------------------------
auto new_screen = std::make_unique<MainScreen>(_renderer, "tomato.tox");
return new_screen.release();
}

21
src/start_screen.hpp Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include "./screen.hpp"
// fwd
extern "C" {
struct SDL_Renderer;
} // C
struct StartScreen final : public Screen {
SDL_Renderer* _renderer;
StartScreen(void) = delete;
StartScreen(SDL_Renderer* renderer);
~StartScreen(void) = default;
// return nullptr if not next
// sets bool quit to true if exit
Screen* poll(bool&) override;
};

13
src/texture_uploader.hpp Normal file
View File

@ -0,0 +1,13 @@
#pragma once
#include <cstdint>
struct TextureUploaderI {
virtual ~TextureUploaderI(void) {}
//virtual uint64_t uploadRGBA(const uint8_t* data, uint64_t data_size) = 0;
virtual uint64_t uploadRGBA(const uint8_t* data, uint32_t width, uint32_t height) = 0;
virtual void destroy(uint64_t tex_id) = 0;
};

133
src/tox_client.cpp Normal file
View File

@ -0,0 +1,133 @@
#include "./tox_client.hpp"
#include <sodium.h>
#include <vector>
#include <fstream>
#include <iostream>
#include <cassert>
ToxClient::ToxClient(std::string_view save_path) :
_tox_profile_path(save_path)
{
TOX_ERR_OPTIONS_NEW err_opt_new;
Tox_Options* options = tox_options_new(&err_opt_new);
assert(err_opt_new == TOX_ERR_OPTIONS_NEW::TOX_ERR_OPTIONS_NEW_OK);
std::vector<uint8_t> profile_data{};
if (!_tox_profile_path.empty()) {
std::ifstream ifile{_tox_profile_path, std::ios::binary};
if (ifile.is_open()) {
std::cout << "TOX loading save " << _tox_profile_path << "\n";
// fill savedata
while (ifile.good()) {
auto ch = ifile.get();
if (ch == EOF) {
break;
} else {
profile_data.push_back(ch);
}
}
if (profile_data.empty()) {
std::cerr << "empty tox save\n";
} else {
// set options
tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_TOX_SAVE);
tox_options_set_savedata_data(options, profile_data.data(), profile_data.size());
}
ifile.close(); // do i need this?
}
}
TOX_ERR_NEW err_new;
_tox = tox_new(options, &err_new);
tox_options_free(options);
if (err_new != TOX_ERR_NEW_OK) {
std::cerr << "tox_new failed with error code " << err_new << "\n";
throw std::runtime_error{"tox failed"};
}
// no callbacks, use events
tox_events_init(_tox);
// dht bootstrap
{
struct DHT_node {
const char *ip;
uint16_t port;
const char key_hex[TOX_PUBLIC_KEY_SIZE*2 + 1]; // 1 for null terminator
unsigned char key_bin[TOX_PUBLIC_KEY_SIZE];
};
DHT_node nodes[] =
{
// TODO: more/diff nodes
// you can change or add your own bs and tcprelays here, ideally closer to you
{"tox.plastiras.org", 443, "8E8B63299B3D520FB377FE5100E65E3322F7AE5B20A0ACED2981769FC5B43725", {}}, // LU tha14
{"tox2.plastiras.org", 33445, "B6626D386BE7E3ACA107B46F48A5C4D522D29281750D44A0CBA6A2721E79C951", {}}, // DE tha14
};
for (size_t i = 0; i < sizeof(nodes)/sizeof(DHT_node); i ++) {
sodium_hex2bin(
nodes[i].key_bin, sizeof(nodes[i].key_bin),
nodes[i].key_hex, sizeof(nodes[i].key_hex)-1,
NULL, NULL, NULL
);
tox_bootstrap(_tox, nodes[i].ip, nodes[i].port, nodes[i].key_bin, NULL);
// TODO: use extra tcp option to avoid error msgs
// ... this is hardcore
tox_add_tcp_relay(_tox, nodes[i].ip, nodes[i].port, nodes[i].key_bin, NULL);
}
}
}
ToxClient::~ToxClient(void) {
tox_kill(_tox);
}
bool ToxClient::iterate(void) {
Tox_Err_Events_Iterate err_e_it = TOX_ERR_EVENTS_ITERATE_OK;
auto* events = tox_events_iterate(_tox, false, &err_e_it);
if (err_e_it == TOX_ERR_EVENTS_ITERATE_OK && events != nullptr) {
_subscriber_raw(events);
// forward events to event handlers
dispatchEvents(events);
}
tox_events_free(events);
if (_tox_profile_dirty) {
saveToxProfile();
}
return true;
}
void ToxClient::subscribeRaw(std::function<void(const Tox_Events*)> fn) {
_subscriber_raw = fn;
}
void ToxClient::saveToxProfile(void) {
if (_tox_profile_path.empty()) {
return;
}
std::cout << "TOX saving\n";
std::vector<uint8_t> data{};
data.resize(tox_get_savedata_size(_tox));
tox_get_savedata(_tox, data.data());
std::ofstream ofile{_tox_profile_path, std::ios::binary};
// TODO: improve
for (const auto& ch : data) {
ofile.put(ch);
}
_tox_profile_dirty = false;
}

49
src/tox_client.hpp Normal file
View File

@ -0,0 +1,49 @@
#pragma once
#include <solanaceae/toxcore/tox_default_impl.hpp>
#include <solanaceae/toxcore/tox_event_interface.hpp>
#include <solanaceae/toxcore/tox_event_provider_base.hpp>
#include <string>
#include <string_view>
#include <vector>
#include <functional>
struct ToxEventI;
class ToxClient : public ToxDefaultImpl, public ToxEventProviderBase {
private:
bool _should_stop {false};
std::function<void(const Tox_Events*)> _subscriber_raw {[](const auto*) {}}; // only 1?
std::string _self_name;
std::string _tox_profile_path;
bool _tox_profile_dirty {true}; // set in callbacks
public:
//ToxClient(/*const CommandLine& cl*/);
ToxClient(std::string_view save_path);
~ToxClient(void);
public: // tox stuff
Tox* getTox(void) { return _tox; }
void setDirty(void) { _tox_profile_dirty = true; }
// returns false when we shoul stop the program
bool iterate(void);
void stop(void); // let it know it should exit
void setToxProfilePath(const std::string& new_path) { _tox_profile_path = new_path; }
void setSelfName(std::string_view new_name) { _self_name = new_name; toxSelfSetName(new_name); }
public: // raw events
void subscribeRaw(std::function<void(const Tox_Events*)> fn);
private:
void saveToxProfile(void);
};