infinipaint/src/DrawingProgram/DrawingProgram.cpp

745 lines
30 KiB
C++
Raw Normal View History

2025-07-06 20:43:53 +03:00
#include "DrawingProgram.hpp"
#include "Helpers/Hashes.hpp"
#include "Helpers/MathExtras.hpp"
#include "Helpers/SCollision.hpp"
#include <include/core/SkPaint.h>
#include <include/core/SkVertices.h>
#include "../DrawCamera.hpp"
#include "../DrawComponents/DrawComponent.hpp"
#include "EllipseDrawTool.hpp"
#include "InkDropperTool.hpp"
#include "../InputManager.hpp"
#include "../SharedTypes.hpp"
#include "../Server/CommandList.hpp"
#include <optional>
#include <ranges>
#include <Helpers/ConvertVec.hpp>
#include <cereal/types/vector.hpp>
#include <iostream>
#include "../DrawComponents/DrawImage.hpp"
#include <include/core/SkImage.h>
#include <include/core/SkSurface.h>
#include "../World.hpp"
#include "../MainProgram.hpp"
2025-08-02 12:31:16 +03:00
#include "LassoSelectTool.hpp"
2025-07-06 20:43:53 +03:00
#include <Helpers/Logger.hpp>
2025-07-24 19:35:51 +03:00
#include <Helpers/Parallel.hpp>
#include <cereal/types/unordered_set.hpp>
2025-08-02 12:31:16 +03:00
#include <include/core/SkPathEffect.h>
#include <include/effects/SkDashPathEffect.h>
#include <chrono>
2025-07-19 19:41:25 +03:00
2025-07-06 20:43:53 +03:00
DrawingProgram::DrawingProgram(World& initWorld):
world(initWorld),
2025-07-20 15:24:44 +03:00
compCache(*this),
2025-07-06 20:43:53 +03:00
components([&](){ return world.get_new_id(); }),
brushTool(*this),
eraserTool(*this),
rectSelectTool(*this),
2025-08-02 12:31:16 +03:00
lassoSelectTool(*this),
2025-07-06 20:43:53 +03:00
rectDrawTool(*this),
ellipseDrawTool(*this),
textBoxTool(*this),
inkDropperTool(*this),
screenshotTool(*this),
editTool(*this),
imageTool(*this),
selection(*this)
2025-07-06 20:43:53 +03:00
{
components.updateCallback = [&]() {
if(&world == world.main.world.get())
world.main.drawProgCache.refresh = true;
};
components.clientInsertCallback = [&](const CollabListType::ObjectInfoPtr& c) {
2025-07-28 11:09:59 +03:00
if(addToCompCacheOnInsert)
compCache.add_component(c);
DrawComponentType t = c->obj->get_type();
if(t == DRAWCOMPONENT_IMAGE || t == DRAWCOMPONENT_TEXTBOX)
updateableComponents.emplace(c->obj);
};
components.clientEraseCallback = [&](const CollabListType::ObjectInfoPtr& c) {
compCache.erase_component(c);
2025-07-27 16:30:45 +03:00
selection.erase_component(c);
delayedUpdateComponents.erase(c->obj);
updateableComponents.erase(c->obj);
};
2025-07-23 16:04:16 +03:00
components.clientInsertOrderedVectorCallback = [&](const std::vector<CollabListType::ObjectInfoPtr>& comps) {
for(auto& c : comps)
components.clientInsertCallback(c);
};
2025-07-19 19:41:25 +03:00
components.clientServerLastPosShiftCallback = [&](uint64_t lastShiftPos) {
compCache.invalidate_cache_before_pos(lastShiftPos);
};
2025-07-06 20:43:53 +03:00
}
void DrawingProgram::init_client_callbacks() {
world.con.client_add_recv_callback(CLIENT_UPDATE_COMPONENT, [&](cereal::PortableBinaryInputArchive& message) {
ServerClientID id;
2025-07-31 09:34:17 +03:00
message(id);
auto objPtr = components.get_item_by_id(id);
if(!objPtr)
2025-07-06 20:43:53 +03:00
return;
std::shared_ptr<DrawComponent>& comp = objPtr->obj;
2025-07-31 09:34:17 +03:00
if((std::chrono::steady_clock::now() - comp->lastUpdateTime) > CLIENT_DRAWCOMP_DELAY_TIMER_DURATION) {
delayedUpdateComponents.erase(comp);
2025-07-06 20:43:53 +03:00
message(*comp);
2025-07-31 09:34:17 +03:00
comp->commit_update(*this);
2025-07-06 20:43:53 +03:00
}
else {
2025-07-31 09:34:17 +03:00
auto delayedUpdatePtr = DrawComponent::allocate_comp_type(comp->get_type());
message(*delayedUpdatePtr);
delayedUpdateComponents[comp] = delayedUpdatePtr;
2025-07-06 20:43:53 +03:00
}
});
world.con.client_add_recv_callback(CLIENT_TRANSFORM_MANY_COMPONENTS, [&](cereal::PortableBinaryInputArchive& message) {
std::vector<std::pair<ServerClientID, CoordSpaceHelper>> transforms;
message(transforms);
if(transforms.size() >= DrawingProgramCache::MINIMUM_COMPONENTS_TO_START_REBUILD) {
std::atomic<size_t> objsActuallyTransformed = 0;
parallel_loop_container(transforms, [&](auto& p) {
auto& [id, coords] = p;
auto objPtr = components.get_item_by_id(id);
if(!objPtr || selection.is_selected(objPtr)) // Whatever transformation we're doing right now will overwrite this transformation, so we can ignore this message
return;
std::shared_ptr<DrawComponent>& comp = objPtr->obj;
if(comp->coords == coords)
return;
comp->coords = coords;
objsActuallyTransformed++;
2025-07-31 09:34:17 +03:00
comp->commit_transform(*this, false);
});
if(objsActuallyTransformed != 0)
force_rebuild_cache();
}
else {
for(auto& [id, coords] : transforms) {
auto objPtr = components.get_item_by_id(id);
if(!objPtr || selection.is_selected(objPtr)) // Whatever transformation we're doing right now will overwrite this transformation, so we can ignore this message
continue;
std::shared_ptr<DrawComponent>& comp = objPtr->obj;
if(comp->coords == coords)
return;
comp->coords = coords;
2025-07-31 09:34:17 +03:00
comp->commit_transform(*this);
}
}
2025-07-06 20:43:53 +03:00
});
world.con.client_add_recv_callback(CLIENT_PLACE_SINGLE_COMPONENT, [&](cereal::PortableBinaryInputArchive& message) {
2025-07-06 20:43:53 +03:00
uint64_t insertPosition;
DrawComponentType type;
message(insertPosition, type);
ServerClientID newObjectID;
auto newObj = DrawComponent::allocate_comp_type(type);
message(newObjectID, newObj->coords, *newObj);
bool didntExistPreviously = components.server_insert(insertPosition, newObjectID, newObj);
if(didntExistPreviously)
2025-07-31 09:34:17 +03:00
newObj->commit_update(*this);
2025-07-06 20:43:53 +03:00
});
world.con.client_add_recv_callback(CLIENT_PLACE_MANY_COMPONENTS, [&](cereal::PortableBinaryInputArchive& message) {
std::vector<CollabListType::ObjectInfoPtr> sortedPlacedComponents;
SendOrderedComponentVectorOp a{&sortedPlacedComponents};
message(a);
std::vector<bool> didntExistPreviously = components.server_insert_ordered_vector(sortedPlacedComponents);
2025-07-27 16:30:45 +03:00
bool isSingleThread = sortedPlacedComponents.size() < DrawingProgramCache::MINIMUM_COMPONENTS_TO_START_REBUILD;
parallel_loop_container(sortedPlacedComponents, [&](const auto& c){
2025-07-31 09:34:17 +03:00
c->obj->commit_update(*this, isSingleThread);
2025-07-27 16:30:45 +03:00
}, isSingleThread);
if(!isSingleThread)
force_rebuild_cache();
});
world.con.client_add_recv_callback(CLIENT_ERASE_SINGLE_COMPONENT, [&](cereal::PortableBinaryInputArchive& message) {
ServerClientID idToErase;
message(idToErase);
components.server_erase(idToErase);
});
world.con.client_add_recv_callback(CLIENT_ERASE_MANY_COMPONENTS, [&](cereal::PortableBinaryInputArchive& message) {
std::unordered_set<ServerClientID> idsToErase;
message(idsToErase);
components.server_erase_set(idsToErase);
2025-07-06 20:43:53 +03:00
});
}
void DrawingProgram::check_delayed_update_timers() {
2025-07-31 09:34:17 +03:00
std::erase_if(delayedUpdateComponents, [&](auto& p) {
auto& [comp, delayedUpdatePtr] = p;
return comp->check_timers(*this, delayedUpdatePtr);
});
}
void DrawingProgram::check_updateable_components() {
for(auto& comp : updateableComponents)
comp->update(*this);
}
void DrawingProgram::parallel_loop_all_components(std::function<void(const std::shared_ptr<CollabList<std::shared_ptr<DrawComponent>, ServerClientID>::ObjectInfo>&)> func) {
2025-07-20 15:24:44 +03:00
parallel_loop_container(components.client_list(), func);
2025-07-06 20:43:53 +03:00
}
std::unordered_set<ServerClientID> DrawingProgram::get_used_resources() {
std::unordered_set<ServerClientID> toRet;
for(auto& c : components.client_list())
c->obj->get_used_resources(toRet);
return toRet;
}
void DrawingProgram::toolbar_gui() {
Toolbar& t = world.main.toolbar;
CLAY({
.layout = {
.sizing = {.width = CLAY_SIZING_FIT(0), .height = CLAY_SIZING_FIT(0)},
.padding = CLAY_PADDING_ALL(t.io->theme->padding1),
.childGap = t.io->theme->childGap1,
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP},
.layoutDirection = CLAY_TOP_TO_BOTTOM
},
.backgroundColor = convert_vec4<Clay_Color>(t.io->theme->backColor1),
.cornerRadius = CLAY_CORNER_RADIUS(t.io->theme->windowCorners1),
.border = {.color = convert_vec4<Clay_Color>(t.io->theme->backColor2), .width = CLAY_BORDER_OUTSIDE(t.io->theme->windowBorders1)}
}) {
t.gui.obstructing_window();
if(t.gui.svg_icon_button("Brush Toolbar Button", "data/icons/brush.svg", controls.selectedTool == TOOL_BRUSH)) { controls.selectedTool = TOOL_BRUSH; }
if(t.gui.svg_icon_button("Eraser Toolbar Button", "data/icons/eraser.svg", controls.selectedTool == TOOL_ERASER)) { controls.selectedTool = TOOL_ERASER; }
if(t.gui.svg_icon_button("Text Toolbar Button", "data/icons/text.svg", controls.selectedTool == TOOL_TEXTBOX)) { controls.selectedTool = TOOL_TEXTBOX; }
if(t.gui.svg_icon_button("Ellipse Toolbar Button", "data/icons/circle.svg", controls.selectedTool == TOOL_ELLIPSE)) { controls.selectedTool = TOOL_ELLIPSE; }
if(t.gui.svg_icon_button("Rect Toolbar Button", "data/icons/rectangle.svg", controls.selectedTool == TOOL_RECTANGLE)) { controls.selectedTool = TOOL_RECTANGLE; }
if(t.gui.svg_icon_button("RectSelect Toolbar Button", "data/icons/rectselect.svg", controls.selectedTool == TOOL_RECTSELECT)) { controls.selectedTool = TOOL_RECTSELECT; }
2025-08-02 12:31:16 +03:00
if(t.gui.svg_icon_button("LassoSelect Toolbar Button", "data/icons/close.svg", controls.selectedTool == TOOL_LASSOSELECT)) { controls.selectedTool = TOOL_LASSOSELECT; }
2025-07-06 20:43:53 +03:00
if(t.gui.svg_icon_button("Edit Toolbar Button", "data/icons/cursor.svg", controls.selectedTool == TOOL_EDIT)) { controls.selectedTool = TOOL_EDIT; }
if(t.gui.svg_icon_button("Inkdropper Toolbar Button", "data/icons/eyedropper.svg", controls.selectedTool == TOOL_INKDROPPER)) { controls.selectedTool = TOOL_INKDROPPER; }
if(t.gui.svg_icon_button("Screenshot Toolbar Button", "data/icons/camera.svg", controls.selectedTool == TOOL_SCREENSHOT)) { controls.selectedTool = TOOL_SCREENSHOT; }
CLAY({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(40), .height = CLAY_SIZING_FIXED(40)}}}) {
if(t.gui.color_button("Foreground Color", &controls.foregroundColor, &controls.foregroundColor == t.colorLeft)) {
t.color_selector_left(&controls.foregroundColor == t.colorLeft ? nullptr : &controls.foregroundColor);
}
}
CLAY({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(40), .height = CLAY_SIZING_FIXED(40)}}}) {
if(t.gui.color_button("Background Color", &controls.backgroundColor, &controls.backgroundColor == t.colorLeft))
t.color_selector_left(&controls.backgroundColor == t.colorLeft ? nullptr : &controls.backgroundColor);
}
}
}
void DrawingProgram::tool_options_gui() {
Toolbar& t = world.main.toolbar;
float minGUIWidth = controls.selectedTool == TOOL_SCREENSHOT ? 300 : 200;
CLAY({
.layout = {
.sizing = {.width = CLAY_SIZING_FIT(minGUIWidth), .height = CLAY_SIZING_FIT(0)},
.padding = CLAY_PADDING_ALL(t.io->theme->padding1),
.childGap = t.io->theme->childGap1,
.childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP},
.layoutDirection = CLAY_TOP_TO_BOTTOM
},
.backgroundColor = convert_vec4<Clay_Color>(t.io->theme->backColor1),
.cornerRadius = CLAY_CORNER_RADIUS(t.io->theme->windowCorners1),
.border = {.color = convert_vec4<Clay_Color>(t.io->theme->backColor2), .width = CLAY_BORDER_OUTSIDE(t.io->theme->windowBorders1)}
}) {
t.gui.obstructing_window();
switch(controls.selectedTool) {
case TOOL_BRUSH:
brushTool.gui_toolbox();
break;
case TOOL_ERASER:
eraserTool.gui_toolbox();
break;
2025-08-02 12:31:16 +03:00
case TOOL_LASSOSELECT:
lassoSelectTool.gui_toolbox();
break;
2025-07-06 20:43:53 +03:00
case TOOL_RECTSELECT:
rectSelectTool.gui_toolbox();
break;
case TOOL_RECTANGLE:
rectDrawTool.gui_toolbox();
break;
case TOOL_ELLIPSE:
ellipseDrawTool.gui_toolbox();
break;
case TOOL_TEXTBOX:
textBoxTool.gui_toolbox();
break;
case TOOL_INKDROPPER:
inkDropperTool.gui_toolbox();
break;
case TOOL_SCREENSHOT:
screenshotTool.gui_toolbox();
break;
case TOOL_EDIT:
editTool.gui_toolbox();
break;
}
}
}
void DrawingProgram::update() {
if(controls.selectedTool == TOOL_BRUSH && world.main.input.pen.isEraser && !temporaryEraser) {
controls.selectedTool = TOOL_ERASER;
temporaryEraser = true;
}
else if(controls.selectedTool == TOOL_ERASER && !world.main.input.pen.isEraser && temporaryEraser) {
controls.selectedTool = TOOL_BRUSH;
temporaryEraser = false;
}
if(controls.selectedTool != controls.previousSelected) {
controls.previousSelected = controls.selectedTool;
reset_tools();
}
controls.cursorHoveringOverCanvas = !world.main.toolbar.io->hoverObstructed;
controls.leftClick = controls.cursorHoveringOverCanvas && world.main.input.mouse.leftClicks;
if(controls.leftClick) {
controls.leftClickHeld = true;
world.main.toolbar.paintPopupLocation = std::nullopt;
}
if(controls.leftClickHeld && !world.main.input.mouse.leftDown) {
controls.leftClick = false;
controls.leftClickHeld = false;
controls.leftClickReleased = true;
}
if(controls.cursorHoveringOverCanvas && (world.main.input.mouse.rightClicks || world.main.input.pen.buttons[world.main.toolbar.tabletOptions.rightClickButton].pressed)) {
if(world.main.toolbar.paintPopupLocation)
world.main.toolbar.paintPopupLocation = std::nullopt;
else
world.main.toolbar.paintPopupLocation = world.main.input.mouse.pos / world.main.window.scale;
}
controls.previousMouseWorldPos = controls.currentMouseWorldPos;
controls.currentMouseWorldPos = world.get_mouse_world_pos();
if(addFileInNextFrame) {
add_file_to_canvas_by_path_execute(addFileInfo.first, addFileInfo.second);
addFileInNextFrame = false;
}
2025-07-06 20:43:53 +03:00
drag_drop_update();
if(world.main.input.key(InputManager::KEY_DRAW_TOOL_BRUSH).pressed)
controls.selectedTool = TOOL_BRUSH;
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_ERASER).pressed)
controls.selectedTool = TOOL_ERASER;
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_RECTSELECT).pressed)
controls.selectedTool = TOOL_RECTSELECT;
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_RECTANGLE).pressed)
controls.selectedTool = TOOL_RECTANGLE;
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_ELLIPSE).pressed)
controls.selectedTool = TOOL_ELLIPSE;
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_TEXTBOX).pressed)
controls.selectedTool = TOOL_TEXTBOX;
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_INKDROPPER).pressed)
controls.selectedTool = TOOL_INKDROPPER;
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_SCREENSHOT).pressed)
controls.selectedTool = TOOL_SCREENSHOT;
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_EDIT).pressed)
controls.selectedTool = TOOL_EDIT;
switch(controls.selectedTool) {
case TOOL_BRUSH:
brushTool.tool_update();
break;
case TOOL_ERASER:
eraserTool.tool_update();
break;
2025-08-02 12:31:16 +03:00
case TOOL_LASSOSELECT:
lassoSelectTool.tool_update();
break;
2025-07-06 20:43:53 +03:00
case TOOL_RECTSELECT:
rectSelectTool.tool_update();
break;
case TOOL_RECTANGLE:
rectDrawTool.tool_update();
break;
case TOOL_ELLIPSE:
ellipseDrawTool.tool_update();
break;
case TOOL_TEXTBOX:
textBoxTool.tool_update();
break;
case TOOL_INKDROPPER:
inkDropperTool.tool_update();
break;
case TOOL_SCREENSHOT:
screenshotTool.tool_update();
break;
case TOOL_EDIT:
editTool.tool_update();
break;
};
if(controls.leftClickReleased)
controls.leftClickReleased = false;
check_delayed_update_timers();
check_updateable_components();
selection.update();
if(controls.selectedTool == TOOL_ERASER)
compCache.test_rebuild_dont_include_set_dont_include_nodes(components.client_list(), eraserTool.erasedComponents, eraserTool.erasedBVHNodes);
else if(controls.selectedTool == TOOL_RECTSELECT)
compCache.test_rebuild_dont_include_set(components.client_list(), selection.get_selected_set());
else
compCache.test_rebuild(components.client_list());
}
bool DrawingProgram::prevent_undo_or_redo() {
switch(controls.selectedTool) {
case TOOL_BRUSH:
return brushTool.prevent_undo_or_redo();
case TOOL_ERASER:
return eraserTool.prevent_undo_or_redo();
2025-08-02 12:31:16 +03:00
case TOOL_LASSOSELECT:
return lassoSelectTool.prevent_undo_or_redo();
case TOOL_RECTSELECT:
return rectSelectTool.prevent_undo_or_redo();
case TOOL_RECTANGLE:
return rectDrawTool.prevent_undo_or_redo();
case TOOL_ELLIPSE:
return ellipseDrawTool.prevent_undo_or_redo();
case TOOL_TEXTBOX:
return textBoxTool.prevent_undo_or_redo();
case TOOL_INKDROPPER:
return inkDropperTool.prevent_undo_or_redo();
case TOOL_SCREENSHOT:
return screenshotTool.prevent_undo_or_redo();
case TOOL_EDIT:
return editTool.prevent_undo_or_redo();
};
return false;
}
2025-08-02 12:31:16 +03:00
SkPaint DrawingProgram::select_tool_line_paint() {
SkScalar intervals[] = {10, 5};
float timeSinceEpoch = std::chrono::duration_cast<std::chrono::duration<float>>(std::chrono::steady_clock::now().time_since_epoch()).count();
sk_sp<SkPathEffect> lassoLineDashEffect = SkDashPathEffect::Make(intervals, 2, -std::fmod(timeSinceEpoch * 50, 15));
SkPaint selectLinePaint;
selectLinePaint.setStyle(SkPaint::kStroke_Style);
selectLinePaint.setStrokeWidth(3);
selectLinePaint.setColor4f(world.canvasTheme.toolFrontColor);
selectLinePaint.setPathEffect(lassoLineDashEffect);
return selectLinePaint;
}
void DrawingProgram::force_rebuild_cache() {
if(controls.selectedTool == TOOL_ERASER)
compCache.test_rebuild_dont_include_set_dont_include_nodes(components.client_list(), eraserTool.erasedComponents, eraserTool.erasedBVHNodes, true);
else if(controls.selectedTool == TOOL_RECTSELECT)
compCache.test_rebuild_dont_include_set(components.client_list(), selection.get_selected_set(), true);
else
compCache.test_rebuild(components.client_list(), true);
2025-07-06 20:43:53 +03:00
}
void DrawingProgram::drag_drop_update() {
if(controls.cursorHoveringOverCanvas) {
for(auto& droppedItem : world.main.input.droppedItems) {
if(droppedItem.isFile && std::filesystem::is_regular_file(droppedItem.droppedData)) {
#ifdef __EMSCRIPTEN_
add_file_to_canvas_by_path(droppedItem.droppedData, world.main.input.mouse.pos, true);
#else
add_file_to_canvas_by_path(droppedItem.droppedData, droppedItem.pos, true);
#endif
}
2025-07-06 20:43:53 +03:00
}
}
}
void DrawingProgram::add_file_to_canvas_by_path(const std::string& filePath, Vector2f dropPos, bool addInSameThread) {
if(addInSameThread)
add_file_to_canvas_by_path_execute(filePath, dropPos);
else if(!addFileInNextFrame) {
addFileInfo = {filePath, dropPos};
addFileInNextFrame = true;
}
}
void DrawingProgram::add_file_to_canvas_by_path_execute(const std::string& filePath, Vector2f dropPos) {
ServerClientID imageID = world.rMan.add_resource_file(filePath);
std::shared_ptr<ResourceDisplay> display = world.rMan.get_display_data(imageID);
Vector2f imTrueDim = display->get_dimensions();
auto img(std::make_shared<DrawImage>());
img->coords = world.drawData.cam.c;
float imWidth = imTrueDim.x() / (imTrueDim.x() + imTrueDim.y());
float imHeight = imTrueDim.y() / (imTrueDim.x() + imTrueDim.y());
Vector2f imDim = Vector2f{world.main.window.size.x() * imWidth, world.main.window.size.x() * imHeight} * display->get_dimension_scale();
img->d.p1 = dropPos - imDim;
img->d.p2 = dropPos + imDim;
img->d.imageID = imageID;
2025-07-31 09:34:17 +03:00
img->commit_update(*this);
uint64_t placement = components.client_list().size();
2025-07-27 16:30:45 +03:00
auto objAdd = components.client_insert(placement, img);
img->client_send_place(*this);
2025-07-27 16:30:45 +03:00
add_undo_place_component(objAdd);
}
void DrawingProgram::add_file_to_canvas_by_data(const std::string& fileName, std::string_view fileBuffer, Vector2f dropPos) {
ResourceData newResource;
newResource.data = std::make_shared<std::string>(fileBuffer);
newResource.name = fileName;
ServerClientID imageID = world.rMan.add_resource(newResource);
std::shared_ptr<ResourceDisplay> display = world.rMan.get_display_data(imageID);
Vector2f imTrueDim = display->get_dimensions();
auto img(std::make_shared<DrawImage>());
img->coords = world.drawData.cam.c;
float imWidth = imTrueDim.x() / (imTrueDim.x() + imTrueDim.y());
float imHeight = imTrueDim.y() / (imTrueDim.x() + imTrueDim.y());
Vector2f imDim = Vector2f{world.main.window.size.x() * imWidth, world.main.window.size.x() * imHeight} * display->get_dimension_scale();
img->d.p1 = dropPos - imDim;
img->d.p2 = dropPos + imDim;
img->d.imageID = imageID;
2025-07-31 09:34:17 +03:00
img->commit_update(*this);
uint64_t placement = components.client_list().size();
2025-07-27 16:30:45 +03:00
auto objAdd = components.client_insert(placement, img);
img->client_send_place(*this);
2025-07-27 16:30:45 +03:00
add_undo_place_component(objAdd);
}
2025-07-06 20:43:53 +03:00
void DrawingProgram::reset_tools() {
2025-08-02 12:31:16 +03:00
lassoSelectTool.reset_tool();
2025-07-06 20:43:53 +03:00
rectSelectTool.reset_tool();
textBoxTool.reset_tool();
brushTool.reset_tool();
rectDrawTool.reset_tool();
ellipseDrawTool.reset_tool();
editTool.reset_tool();
2025-07-23 16:04:16 +03:00
eraserTool.reset_tool();
2025-07-06 20:43:53 +03:00
}
void DrawingProgram::initialize_draw_data(cereal::PortableBinaryInputArchive& a) {
uint64_t compCount;
a(compCount);
for(uint64_t i = 0; i < compCount; i++) {
DrawComponentType t;
ServerClientID id;
a(t, id);
auto newComp = DrawComponent::allocate_comp_type(t);
a(newComp->coords, *newComp);
components.init_emplace_back(id, newComp);
}
parallel_loop_all_components([&](const auto& c){
2025-07-31 09:34:17 +03:00
c->obj->commit_update(*this, false);
});
compCache.test_rebuild(components.client_list(), true);
2025-07-06 20:43:53 +03:00
}
void DrawingProgram::client_erase_set(std::unordered_set<CollabListType::ObjectInfoPtr> erasedComponents) {
std::unordered_set<ServerClientID> idsToErase;
for(auto& c : erasedComponents)
idsToErase.emplace(c->id);
DrawComponent::client_send_erase_set(*this, idsToErase);
components.client_erase_set(erasedComponents);
add_undo_erase_components(erasedComponents);
}
2025-07-27 16:30:45 +03:00
void DrawingProgram::add_undo_place_component(const CollabListType::ObjectInfoPtr& objToUndo) {
2025-07-06 20:43:53 +03:00
world.undo.push(UndoManager::UndoRedoPair{
2025-07-27 16:30:45 +03:00
[&, objToUndo]() {
objToUndo->obj->client_send_erase(*this);
components.client_erase(objToUndo);
2025-07-06 20:43:53 +03:00
reset_tools();
return true;
},
2025-07-27 16:30:45 +03:00
[&, objToUndo]() {
if(objToUndo->obj->collabListInfo.lock())
2025-07-06 20:43:53 +03:00
return false;
2025-07-27 16:30:45 +03:00
components.client_insert(objToUndo);
objToUndo->obj->client_send_place(*this);
2025-07-06 20:43:53 +03:00
reset_tools();
return true;
}
});
}
void DrawingProgram::invalidate_cache_at_component(const CollabListType::ObjectInfoPtr& objToCheck) {
if(selection.is_selected(objToCheck))
selection.invalidate_cache_at_optional_aabb_before_pos(objToCheck->obj->worldAABB, objToCheck->pos);
else
compCache.invalidate_cache_at_optional_aabb_before_pos(objToCheck->obj->worldAABB, objToCheck->pos);
}
void DrawingProgram::preupdate_component(const CollabListType::ObjectInfoPtr& objToCheck) {
if(selection.is_selected(objToCheck))
selection.preupdate_component(objToCheck);
else
compCache.preupdate_component(objToCheck);
}
2025-07-28 11:09:59 +03:00
void DrawingProgram::add_undo_place_components(const std::unordered_set<CollabListType::ObjectInfoPtr>& objSetToUndo) {
world.undo.push(UndoManager::UndoRedoPair{
2025-07-28 11:09:59 +03:00
[&, objSetToUndo = objSetToUndo]() {
for(auto& comp : objSetToUndo)
if(!comp->obj->collabListInfo.lock())
return false;
std::unordered_set<ServerClientID> idsToErase;
for(auto& c : objSetToUndo)
idsToErase.emplace(c->id);
DrawComponent::client_send_erase_set(*this, idsToErase);
components.client_erase_set(objSetToUndo);
reset_tools();
return true;
},
[&, objSetToUndo = objSetToUndo]() {
std::vector<CollabListType::ObjectInfoPtr> sortedObjects(objSetToUndo.begin(), objSetToUndo.end());
std::sort(sortedObjects.begin(), sortedObjects.end(), [](auto& a, auto& b) {
return a->pos < b->pos;
});
for(auto& comp : sortedObjects)
if(comp->obj->collabListInfo.lock())
return false;
components.client_insert_ordered_vector(sortedObjects);
DrawComponent::client_send_place_many(*this, sortedObjects);
reset_tools();
return true;
}
});
}
void DrawingProgram::add_undo_erase_components(const std::unordered_set<CollabListType::ObjectInfoPtr>& objSetToUndo) {
world.undo.push(UndoManager::UndoRedoPair{
[&, objSetToUndo = objSetToUndo]() {
std::vector<CollabListType::ObjectInfoPtr> sortedObjects(objSetToUndo.begin(), objSetToUndo.end());
std::sort(sortedObjects.begin(), sortedObjects.end(), [](auto& a, auto& b) {
return a->pos < b->pos;
});
for(auto& comp : sortedObjects)
if(comp->obj->collabListInfo.lock())
return false;
components.client_insert_ordered_vector(sortedObjects);
DrawComponent::client_send_place_many(*this, sortedObjects);
reset_tools();
return true;
},
2025-07-28 11:09:59 +03:00
[&, objSetToUndo = objSetToUndo]() {
for(auto& comp : objSetToUndo)
if(!comp->obj->collabListInfo.lock())
return false;
std::unordered_set<ServerClientID> idsToErase;
2025-07-28 11:09:59 +03:00
for(auto& c : objSetToUndo)
idsToErase.emplace(c->id);
DrawComponent::client_send_erase_set(*this, idsToErase);
2025-07-28 11:09:59 +03:00
components.client_erase_set(objSetToUndo);
reset_tools();
return true;
}
});
}
2025-07-06 20:43:53 +03:00
ClientPortionID DrawingProgram::get_max_id(ServerPortionID serverID) {
ClientPortionID maxClientID = 0;
auto& cList = components.client_list();
for(auto& comp : cList) {
if(comp->id.first == serverID)
maxClientID = std::max(maxClientID, comp->id.second);
}
return maxClientID;
}
void DrawingProgram::draw_drag_circle(SkCanvas* canvas, const Vector2f& sPos, const SkColor4f& c, const DrawData& drawData, float radiusMultiplier) {
float constantRadius = DRAG_POINT_RADIUS * radiusMultiplier;
float constantThickness = constantRadius * 0.2f;
canvas->drawCircle(sPos.x(), sPos.y(), constantRadius, SkPaint(c));
SkPaint paintOutline(SkColor4f{0.95f, 0.95f, 0.95f, 1.0f});
paintOutline.setStroke(true);
paintOutline.setStrokeWidth(constantThickness);
canvas->drawCircle(sPos.x(), sPos.y(), constantRadius, paintOutline);
}
void DrawingProgram::write_to_file(cereal::PortableBinaryOutputArchive& a) {
auto& cList = components.client_list();
a((uint64_t)cList.size());
for(size_t i = 0; i < cList.size(); i++) {
a(cList[i]->obj->get_type(), cList[i]->id, cList[i]->obj->coords, *cList[i]->obj);
}
}
void DrawingProgram::draw(SkCanvas* canvas, const DrawData& drawData) {
if(drawData.dontUseDrawProgCache) {
parallel_loop_all_components([&](auto& c) {
2025-07-06 20:43:53 +03:00
c->obj->calculate_draw_transform(drawData);
});
for(auto& c : components.client_list()) {
2025-07-06 20:43:53 +03:00
c->obj->draw(canvas, drawData);
c->obj->drawSetupData.shouldDraw = false;
}
2025-07-06 20:43:53 +03:00
}
else {
2025-07-20 15:24:44 +03:00
if(world.main.drawProgCache.disableDrawCache) {
parallel_loop_all_components([&](auto& c) {
2025-07-06 20:43:53 +03:00
c->obj->calculate_draw_transform(drawData);
});
for(auto& c : components.client_list()) {
2025-07-20 15:24:44 +03:00
c->obj->draw(canvas, drawData);
c->obj->drawSetupData.shouldDraw = false;
}
}
else {
2025-07-20 15:24:44 +03:00
SkCanvas* drawProgCacheCanvas = world.main.drawProgCache.surface->getCanvas();
drawProgCacheCanvas->clear(SkColor4f{0.0f, 0.0f, 0.0f, 0.0f});
compCache.refresh_all_draw_cache(drawData);
compCache.draw_components_to_canvas(drawProgCacheCanvas, drawData);
2025-07-25 10:00:39 +03:00
drawProgCacheCanvas->saveLayerAlphaf(nullptr, 1.0f);
2025-07-24 19:35:51 +03:00
selection.draw_components(drawProgCacheCanvas, drawData);
2025-07-25 10:00:39 +03:00
drawProgCacheCanvas->restore();
2025-07-20 15:24:44 +03:00
canvas->drawImage(world.main.drawProgCache.surface->makeTemporaryImage(), 0, 0);
2025-07-24 19:35:51 +03:00
selection.draw_gui(canvas, drawData);
}
2025-07-06 20:43:53 +03:00
}
switch(controls.selectedTool) {
case TOOL_BRUSH:
brushTool.draw(canvas, drawData);
break;
case TOOL_ERASER:
eraserTool.draw(canvas, drawData);
break;
2025-08-02 12:31:16 +03:00
case TOOL_LASSOSELECT:
lassoSelectTool.draw(canvas, drawData);
break;
2025-07-06 20:43:53 +03:00
case TOOL_RECTSELECT:
rectSelectTool.draw(canvas, drawData);
break;
case TOOL_RECTANGLE:
rectDrawTool.draw(canvas, drawData);
break;
case TOOL_ELLIPSE:
ellipseDrawTool.draw(canvas, drawData);
break;
case TOOL_TEXTBOX:
textBoxTool.draw(canvas, drawData);
break;
case TOOL_INKDROPPER:
inkDropperTool.draw(canvas, drawData);
break;
case TOOL_SCREENSHOT:
screenshotTool.draw(canvas, drawData);
break;
case TOOL_EDIT:
editTool.draw(canvas, drawData);
break;
}
}
Vector4f* DrawingProgram::get_foreground_color_ptr() {
return &controls.foregroundColor;
}