2026-05-11 23:06:39 +03:00
|
|
|
/*
|
|
|
|
|
* InfiniPaint
|
|
|
|
|
* Copyright (C) 2025-2026 Yousef Khadadeh
|
|
|
|
|
*
|
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
2025-12-17 14:46:27 +03:00
|
|
|
#include "ClientData.hpp"
|
2025-12-31 16:30:56 +03:00
|
|
|
#include "CommandList.hpp"
|
2025-12-17 14:46:27 +03:00
|
|
|
#include "World.hpp"
|
|
|
|
|
#include <Helpers/NetworkingObjects/NetObjGenericSerializedClass.hpp>
|
|
|
|
|
#include "MainProgram.hpp"
|
2025-12-31 16:30:56 +03:00
|
|
|
#include "ScaleUpCanvas.hpp"
|
2025-12-17 14:46:27 +03:00
|
|
|
|
2026-05-14 20:21:45 +03:00
|
|
|
#include <modules/skparagraph/src/ParagraphBuilderImpl.h>
|
|
|
|
|
#include <modules/skparagraph/include/ParagraphStyle.h>
|
|
|
|
|
#include <modules/skparagraph/include/FontCollection.h>
|
|
|
|
|
#include <modules/skparagraph/include/TextStyle.h>
|
|
|
|
|
#include <modules/skunicode/include/SkUnicode_icu.h>
|
|
|
|
|
|
2025-12-17 14:46:27 +03:00
|
|
|
using namespace NetworkingObjects;
|
|
|
|
|
|
|
|
|
|
enum class ClientDataCommand : uint8_t {
|
|
|
|
|
SET_CURSOR_POS = 0,
|
|
|
|
|
SET_WINDOW_SIZE,
|
|
|
|
|
SET_CAM_COORDS,
|
2025-12-31 16:30:56 +03:00
|
|
|
SEND_CHAT_MESSAGE,
|
|
|
|
|
SET_GRID_SIZE
|
2025-12-17 14:46:27 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ClientData::ClientData() {
|
|
|
|
|
set_from_init_struct(InitStruct());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ClientData::ClientData(const InitStruct& initStruct) {
|
|
|
|
|
set_from_init_struct(initStruct);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClientData::set_from_init_struct(const InitStruct& initStruct) {
|
|
|
|
|
camCoords = initStruct.camCoords;
|
|
|
|
|
windowSize = initStruct.windowSize;
|
|
|
|
|
cursorPos = initStruct.cursorPos;
|
|
|
|
|
cursorColor = initStruct.cursorColor;
|
|
|
|
|
displayName = initStruct.displayName;
|
2025-12-31 16:30:56 +03:00
|
|
|
gridSize = initStruct.gridSize;
|
2025-12-17 14:46:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClientData::register_class(World& world) {
|
|
|
|
|
world.netObjMan.register_class<ClientData, ClientData, ClientData, ClientData>({
|
|
|
|
|
.writeConstructorFuncClient = default_serialize_write_func<ClientData>,
|
|
|
|
|
.readConstructorFuncClient = default_serialize_read_constructor_func<ClientData>,
|
|
|
|
|
.readUpdateFuncClient = [&world](const NetObjTemporaryPtr<ClientData>& o, cereal::PortableBinaryInputArchive& a, const std::shared_ptr<NetServer::ClientData>&) {
|
|
|
|
|
ClientDataCommand command;
|
|
|
|
|
a(command);
|
|
|
|
|
switch(command) {
|
|
|
|
|
case ClientDataCommand::SET_CURSOR_POS:
|
|
|
|
|
a(o->cursorPos);
|
|
|
|
|
break;
|
|
|
|
|
case ClientDataCommand::SET_WINDOW_SIZE:
|
|
|
|
|
a(o->windowSize);
|
|
|
|
|
break;
|
|
|
|
|
case ClientDataCommand::SET_CAM_COORDS:
|
|
|
|
|
a(o->camCoords);
|
|
|
|
|
break;
|
|
|
|
|
case ClientDataCommand::SEND_CHAT_MESSAGE: {
|
|
|
|
|
std::string chatMessage;
|
|
|
|
|
a(chatMessage);
|
|
|
|
|
world.add_chat_message(o->displayName, chatMessage, Toolbar::ChatMessage::Type::NORMAL);
|
2025-12-31 16:30:56 +03:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case ClientDataCommand::SET_GRID_SIZE: {
|
|
|
|
|
uint32_t oldGridSize = o->gridSize;
|
|
|
|
|
a(o->gridSize);
|
|
|
|
|
if(oldGridSize < o->gridSize)
|
2025-12-31 16:43:08 +03:00
|
|
|
world.scale_up(get_canvas_scale_up_amount(o->gridSize, oldGridSize));
|
2025-12-31 16:30:56 +03:00
|
|
|
break;
|
2025-12-17 14:46:27 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
.writeConstructorFuncServer = default_serialize_write_func<ClientData>,
|
|
|
|
|
.readConstructorFuncServer = default_serialize_read_constructor_func<ClientData>,
|
|
|
|
|
.readUpdateFuncServer = [&world](const NetObjTemporaryPtr<ClientData>& o, cereal::PortableBinaryInputArchive& a, const std::shared_ptr<NetServer::ClientData>& c) {
|
|
|
|
|
ClientDataCommand command;
|
|
|
|
|
a(command);
|
|
|
|
|
switch(command) {
|
|
|
|
|
case ClientDataCommand::SET_CURSOR_POS:
|
|
|
|
|
a(o->cursorPos);
|
|
|
|
|
o.send_server_update_to_all_clients_except(c, UNRELIABLE_COMMAND_CHANNEL, [](const NetObjTemporaryPtr<ClientData>& o, cereal::PortableBinaryOutputArchive & a) {
|
|
|
|
|
a(ClientDataCommand::SET_CURSOR_POS, o->cursorPos);
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
case ClientDataCommand::SET_WINDOW_SIZE:
|
|
|
|
|
a(o->windowSize);
|
|
|
|
|
o.send_server_update_to_all_clients_except(c, RELIABLE_COMMAND_CHANNEL, [](const NetObjTemporaryPtr<ClientData>& o, cereal::PortableBinaryOutputArchive & a) {
|
|
|
|
|
a(ClientDataCommand::SET_WINDOW_SIZE, o->windowSize);
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
case ClientDataCommand::SET_CAM_COORDS:
|
|
|
|
|
a(o->camCoords);
|
|
|
|
|
o.send_server_update_to_all_clients_except(c, RELIABLE_COMMAND_CHANNEL, [](const NetObjTemporaryPtr<ClientData>& o, cereal::PortableBinaryOutputArchive & a) {
|
|
|
|
|
a(ClientDataCommand::SET_CAM_COORDS, o->camCoords);
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
case ClientDataCommand::SEND_CHAT_MESSAGE: {
|
|
|
|
|
std::string chatMessage;
|
|
|
|
|
a(chatMessage);
|
|
|
|
|
world.add_chat_message(o->displayName, chatMessage, Toolbar::ChatMessage::Type::NORMAL);
|
2025-12-17 15:50:14 +03:00
|
|
|
o.send_server_update_to_all_clients(RELIABLE_COMMAND_CHANNEL, [&chatMessage](const NetObjTemporaryPtr<ClientData>& o, cereal::PortableBinaryOutputArchive & a) {
|
2025-12-17 14:46:27 +03:00
|
|
|
a(ClientDataCommand::SEND_CHAT_MESSAGE, chatMessage);
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-12-31 16:30:56 +03:00
|
|
|
case ClientDataCommand::SET_GRID_SIZE: {
|
|
|
|
|
uint32_t oldGridSize = o->gridSize;
|
|
|
|
|
a(o->gridSize);
|
|
|
|
|
if(oldGridSize < o->gridSize)
|
2025-12-31 16:43:08 +03:00
|
|
|
world.scale_up(get_canvas_scale_up_amount(o->gridSize, oldGridSize));
|
2025-12-31 16:30:56 +03:00
|
|
|
o.send_server_update_to_all_clients(RELIABLE_COMMAND_CHANNEL, [](const NetObjTemporaryPtr<ClientData>& o, cereal::PortableBinaryOutputArchive & a) {
|
|
|
|
|
a(ClientDataCommand::SET_GRID_SIZE, o->gridSize);
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-12-17 14:46:27 +03:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClientData::set_cursor_pos(const NetworkingObjects::NetObjTemporaryPtr<ClientData>& o, Vector2f newPos) {
|
|
|
|
|
o->cursorPos = newPos;
|
|
|
|
|
o.send_update_to_all(UNRELIABLE_COMMAND_CHANNEL, [](const NetObjTemporaryPtr<ClientData>& o, cereal::PortableBinaryOutputArchive & a) {
|
|
|
|
|
a(ClientDataCommand::SET_CURSOR_POS, o->cursorPos);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClientData::set_window_size(const NetworkingObjects::NetObjTemporaryPtr<ClientData>& o, Vector2f newWindowSize) {
|
|
|
|
|
if(o->windowSize != newWindowSize) {
|
|
|
|
|
o->windowSize = newWindowSize;
|
|
|
|
|
o.send_update_to_all(RELIABLE_COMMAND_CHANNEL, [](const NetObjTemporaryPtr<ClientData>& o, cereal::PortableBinaryOutputArchive & a) {
|
|
|
|
|
a(ClientDataCommand::SET_WINDOW_SIZE, o->windowSize);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClientData::set_camera_coords(const NetworkingObjects::NetObjTemporaryPtr<ClientData>& o, const CoordSpaceHelper& newCoords) {
|
|
|
|
|
if(o->camCoords != newCoords) {
|
|
|
|
|
o->camCoords = newCoords;
|
|
|
|
|
o.send_update_to_all(RELIABLE_COMMAND_CHANNEL, [](const NetObjTemporaryPtr<ClientData>& o, cereal::PortableBinaryOutputArchive & a) {
|
|
|
|
|
a(ClientDataCommand::SET_CAM_COORDS, o->camCoords);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClientData::send_chat_message(const NetworkingObjects::NetObjTemporaryPtr<ClientData>& o, World& world, const std::string& chatMessage) {
|
2025-12-17 15:50:14 +03:00
|
|
|
if(o.get_obj_man()->is_server()) // Clients will receive the message when it returns from the server to ensure correct order
|
|
|
|
|
world.add_chat_message(o->displayName, chatMessage, Toolbar::ChatMessage::Type::NORMAL);
|
2025-12-17 14:46:27 +03:00
|
|
|
o.send_update_to_all(RELIABLE_COMMAND_CHANNEL, [&chatMessage](const NetObjTemporaryPtr<ClientData>& o, cereal::PortableBinaryOutputArchive & a) {
|
|
|
|
|
a(ClientDataCommand::SEND_CHAT_MESSAGE, chatMessage);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-31 16:30:56 +03:00
|
|
|
void ClientData::scale_up_step(const NetworkingObjects::NetObjTemporaryPtr<ClientData>& o, World& world) {
|
|
|
|
|
o->gridSize++;
|
|
|
|
|
world.scale_up(CANVAS_SCALE_UP_STEP);
|
|
|
|
|
o.send_update_to_all(RELIABLE_COMMAND_CHANNEL, [](const NetObjTemporaryPtr<ClientData>& o, cereal::PortableBinaryOutputArchive & a) {
|
|
|
|
|
a(ClientDataCommand::SET_GRID_SIZE, o->gridSize);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 14:46:27 +03:00
|
|
|
void ClientData::set_display_name(const std::string& newDisplayName) {
|
|
|
|
|
displayName = newDisplayName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClientData::set_cursor_color(const Vector3f& newCursorColor) {
|
|
|
|
|
cursorColor = newCursorColor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const CoordSpaceHelper& ClientData::get_cam_coords() const {
|
|
|
|
|
return camCoords;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Vector2f& ClientData::get_window_size() const {
|
|
|
|
|
return windowSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Vector2f& ClientData::get_cursor_pos() const {
|
|
|
|
|
return cursorPos;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-31 16:30:56 +03:00
|
|
|
uint32_t ClientData::get_grid_size() const {
|
|
|
|
|
return gridSize;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 14:46:27 +03:00
|
|
|
const std::string& ClientData::get_display_name() const {
|
|
|
|
|
return displayName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Vector3f& ClientData::get_cursor_color() const {
|
|
|
|
|
return cursorColor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClientData::draw_cursor(SkCanvas* canvas, const DrawData& drawData) const {
|
|
|
|
|
if((drawData.cam.c.inverseScale << 10) > camCoords.inverseScale && drawData.clampDrawMinimum < camCoords.inverseScale) {
|
|
|
|
|
canvas->save();
|
|
|
|
|
camCoords.transform_sk_canvas(canvas, drawData);
|
|
|
|
|
|
|
|
|
|
canvas->translate(cursorPos.x(), cursorPos.y());
|
|
|
|
|
|
2026-05-14 20:21:45 +03:00
|
|
|
{
|
|
|
|
|
SkPaint p(SkColor4f{cursorColor.x(), cursorColor.y(), cursorColor.z(), 0.3f});
|
|
|
|
|
p.setAntiAlias(drawData.skiaAA);
|
|
|
|
|
canvas->drawCircle(0.0f, 0.0f, 4.5f, p);
|
|
|
|
|
p.setStroke(true);
|
|
|
|
|
p.setStrokeWidth(1.0f);
|
|
|
|
|
p.setColor4f({cursorColor.x(), cursorColor.y(), cursorColor.z(), 0.6f});
|
|
|
|
|
canvas->drawCircle(0.0f, 0.0f, 5.0f, p);
|
|
|
|
|
}
|
2025-12-17 14:46:27 +03:00
|
|
|
|
2026-05-14 20:21:45 +03:00
|
|
|
Vector2f bounds;
|
|
|
|
|
std::unique_ptr<skia::textlayout::Paragraph> paragraph;
|
|
|
|
|
{
|
|
|
|
|
skia::textlayout::ParagraphStyle pStyle;
|
|
|
|
|
pStyle.setTextAlign(skia::textlayout::TextAlign::kLeft);
|
|
|
|
|
skia::textlayout::TextStyle tStyle;
|
2026-05-16 21:56:58 +03:00
|
|
|
tStyle.setFontSize(18.0f * drawData.main->g.final_gui_scale());
|
2026-05-14 20:21:45 +03:00
|
|
|
tStyle.setFontFamilies(drawData.main->g.gui.io.fonts->get_default_font_families());
|
|
|
|
|
tStyle.setForegroundPaint(SkPaint{SkColor4f{cursorColor.x(), cursorColor.y(), cursorColor.z(), 0.6f}});
|
|
|
|
|
pStyle.setTextStyle(tStyle);
|
2025-12-17 14:46:27 +03:00
|
|
|
|
2026-05-14 20:21:45 +03:00
|
|
|
skia::textlayout::ParagraphBuilderImpl a(pStyle, drawData.main->g.gui.io.fonts->collection, SkUnicodes::ICU::Make());
|
|
|
|
|
a.addText(displayName.c_str(), displayName.length());
|
|
|
|
|
paragraph = a.Build();
|
|
|
|
|
paragraph->layout(std::numeric_limits<float>::max());
|
|
|
|
|
|
|
|
|
|
bounds = {paragraph->getMaxIntrinsicWidth(), paragraph->getHeight()};
|
|
|
|
|
}
|
2025-12-17 14:46:27 +03:00
|
|
|
|
2026-05-14 20:21:45 +03:00
|
|
|
Vector2f textPos = {6.0f, -bounds.y()};
|
2025-12-17 14:46:27 +03:00
|
|
|
|
2026-05-14 20:21:45 +03:00
|
|
|
SkPaint p3(color_mul_alpha(drawData.main->g.gui.io.theme->backColor1, 0.6f));
|
2026-04-12 17:31:32 +03:00
|
|
|
p3.setAntiAlias(drawData.skiaAA);
|
2026-05-14 20:21:45 +03:00
|
|
|
canvas->drawRoundRect(SkRect::MakeXYWH(textPos.x(), textPos.y(), bounds.x(), bounds.y()), 3.0f, 3.0f, p3);
|
|
|
|
|
|
|
|
|
|
paragraph->paint(canvas, textPos.x(), textPos.y());
|
2025-12-17 14:46:27 +03:00
|
|
|
|
|
|
|
|
canvas->restore();
|
|
|
|
|
}
|
|
|
|
|
}
|