From 859924663ba9d4595761693406d416852dcb0ed1 Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Tue, 16 Dec 2025 12:54:12 +0300 Subject: [PATCH] Reimplemented image components --- src/CanvasComponents/CanvasComponent.hpp | 2 +- src/CanvasComponents/ImageCanvasComponent.cpp | 4 +- src/CanvasComponents/ImageCanvasComponent.hpp | 3 +- src/DrawingProgram/DrawingProgram.cpp | 149 ++++++++++++++++++ src/DrawingProgram/DrawingProgram.hpp | 16 ++ src/ResourceManager.cpp | 12 +- src/ResourceManager.hpp | 4 +- src/Toolbar.cpp | 5 +- 8 files changed, 180 insertions(+), 15 deletions(-) diff --git a/src/CanvasComponents/CanvasComponent.hpp b/src/CanvasComponents/CanvasComponent.hpp index 1412e8f..3baefc8 100644 --- a/src/CanvasComponents/CanvasComponent.hpp +++ b/src/CanvasComponents/CanvasComponent.hpp @@ -18,6 +18,7 @@ class CanvasComponent { virtual void load(cereal::PortableBinaryInputArchive& a) = 0; virtual void save_file(cereal::PortableBinaryOutputArchive& a) const = 0; virtual void load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) = 0; + virtual void update(DrawingProgram& drawP); virtual void set_data_from(const CanvasComponent& other) = 0; protected: @@ -26,7 +27,6 @@ class CanvasComponent { virtual void draw(SkCanvas* canvas, const DrawData& drawData) const = 0; virtual void initialize_draw_data(DrawingProgram& drawP) = 0; - virtual void update(DrawingProgram& drawP); virtual bool collides_within_coords(const SCollision::ColliderCollection& checkAgainst) const = 0; virtual SCollision::AABB get_obj_coord_bounds() const = 0; diff --git a/src/CanvasComponents/ImageCanvasComponent.cpp b/src/CanvasComponents/ImageCanvasComponent.cpp index 886f9db..77ca074 100644 --- a/src/CanvasComponents/ImageCanvasComponent.cpp +++ b/src/CanvasComponents/ImageCanvasComponent.cpp @@ -64,8 +64,8 @@ void ImageCanvasComponent::draw(SkCanvas* canvas, const DrawData& drawData) cons void ImageCanvasComponent::update(DrawingProgram& drawP) { ResourceDisplay* display = drawP.world.drawData.rMan->get_display_data(d.imageID); if(display) { - //if(display->update_draw()) - // drawP.invalidate_cache_at_component(compContainer->objInfo); + if(display->update_draw()) + drawP.invalidate_cache_at_component(compContainer->objInfo.lock()); } } diff --git a/src/CanvasComponents/ImageCanvasComponent.hpp b/src/CanvasComponents/ImageCanvasComponent.hpp index 197200a..94909f0 100644 --- a/src/CanvasComponents/ImageCanvasComponent.hpp +++ b/src/CanvasComponents/ImageCanvasComponent.hpp @@ -23,10 +23,11 @@ class ImageCanvasComponent : public CanvasComponent { NetworkingObjects::NetObjID imageID; } d; + virtual void update(DrawingProgram& drawP) override; + private: virtual void draw(SkCanvas* canvas, const DrawData& drawData) const override; virtual void initialize_draw_data(DrawingProgram& drawP) override; - virtual void update(DrawingProgram& drawP) override; virtual bool collides_within_coords(const SCollision::ColliderCollection& checkAgainst) const override; void create_draw_data(); void create_collider(); diff --git a/src/DrawingProgram/DrawingProgram.cpp b/src/DrawingProgram/DrawingProgram.cpp index ab1fbd3..155bc57 100644 --- a/src/DrawingProgram/DrawingProgram.cpp +++ b/src/DrawingProgram/DrawingProgram.cpp @@ -27,6 +27,7 @@ #include #include #include +#include "../CanvasComponents/ImageCanvasComponent.hpp" #include "EraserTool.hpp" @@ -50,11 +51,17 @@ void DrawingProgram::set_component_list_callbacks() { c->obj->set_owner_obj_info(c); c->obj->commit_update(*this); // Run commit update on insert so that world bounds are calculated compCache.add_component(c); + if(c->obj->get_comp().get_type() == CanvasComponentType::IMAGE) + updateableComponents.emplace(c); }); components->set_erase_callback([&](const CanvasComponentContainer::ObjInfoSharedPtr& c) { compCache.erase_component(c); selection.erase_component(c); drawTool->erase_component(c); + std::erase_if(droppedDownloadingFiles, [&c](auto& downloadingFile) { + return downloadingFile.comp == c; + }); + updateableComponents.erase(c); }); components->set_post_sync_callback([&]() { clear_draw_cache(); @@ -287,6 +294,13 @@ void DrawingProgram::update() { 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; + } + + drag_drop_update(); + if(world.main.input.key(InputManager::KEY_DRAW_TOOL_BRUSH).pressed) switch_to_tool(DrawingProgramToolType::BRUSH); else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_ERASER).pressed) @@ -322,6 +336,9 @@ void DrawingProgram::update() { if(controls.leftClickReleased) controls.leftClickReleased = false; + update_downloading_dropped_files(); + check_updateable_components(); + if(drawTool->get_type() == DrawingProgramToolType::ERASER) { EraserTool* eraserTool = static_cast(drawTool.get()); compCache.test_rebuild_dont_include_set_dont_include_nodes(components->get_data(), eraserTool->erasedComponents, eraserTool->erasedBVHNodes); @@ -332,6 +349,11 @@ void DrawingProgram::update() { compCache.test_rebuild(components->get_data()); } +void DrawingProgram::check_updateable_components() { + for(auto& comp : updateableComponents) + comp->obj->get_comp().update(*this); +} + bool DrawingProgram::is_actual_selection_tool(DrawingProgramToolType typeToCheck) { return typeToCheck == DrawingProgramToolType::RECTSELECT || typeToCheck == DrawingProgramToolType::LASSOSELECT || typeToCheck == DrawingProgramToolType::EDIT; } @@ -383,6 +405,119 @@ void DrawingProgram::force_rebuild_cache() { compCache.test_rebuild(components->get_data(), true); } +void DrawingProgram::drag_drop_update() { + if(controls.cursorHoveringOverCanvas) { + for(auto& droppedItem : world.main.input.droppedItems) { + if(droppedItem.dataPath.has_value() && std::filesystem::is_regular_file(droppedItem.dataPath.value())) { +#ifdef __EMSCRIPTEN_ + add_file_to_canvas_by_path(droppedItem.dataPath.value(), world.main.input.mouse.pos, true); +#else + add_file_to_canvas_by_path(droppedItem.dataPath.value(), droppedItem.pos, true); +#endif + } + else if(droppedItem.dataText.has_value() && is_valid_http_url(droppedItem.dataText.value())) { + CanvasComponentContainer* newContainer = new CanvasComponentContainer(world.netObjMan, CanvasComponentType::IMAGE); + ImageCanvasComponent& img = static_cast(newContainer->get_comp()); + newContainer->coords = world.drawData.cam.c; + Vector2f imDim = Vector2f{100.0f, 100.0f}; + img.d.p1 = droppedItem.pos - imDim; + img.d.p2 = droppedItem.pos + imDim; + img.d.imageID = {0, 0}; + auto newObjInfo = components->push_back_and_send_create(components, newContainer); + droppedDownloadingFiles.emplace_back(newObjInfo, world.main.window.size.cast(), FileDownloader::download_data_from_url(droppedItem.dataText.value())); + } + } + } +} + +void DrawingProgram::update_downloading_dropped_files() { + std::erase_if(droppedDownloadingFiles, [&](auto& downFile) { + switch(downFile.downData->status) { + case FileDownloader::DownloadData::Status::SUCCESS: { + ImageCanvasComponent& img = static_cast(downFile.comp->obj->get_comp()); + Vector2f dropPos = (img.d.p1 + img.d.p2) * 0.5f; + + ResourceData newResource; + newResource.data = std::make_shared(downFile.downData->str); + newResource.name = downFile.downData->fileName; + NetworkingObjects::NetObjID imageID = world.rMan.add_resource(newResource).get_net_id(); + ResourceDisplay* display = world.rMan.get_display_data(imageID); + if(display->get_type() == ResourceDisplay::Type::FILE) { + Logger::get().log("WORLDFATAL", "Failed to parse image from URL"); + components->erase(components, downFile.comp->obj.get_net_id()); + } + else { + Vector2f imTrueDim = display->get_dimensions(); + + float imWidth = imTrueDim.x() / (imTrueDim.x() + imTrueDim.y()); + float imHeight = imTrueDim.y() / (imTrueDim.x() + imTrueDim.y()); + Vector2f imDim = Vector2f{downFile.windowSizeWhenDropped.x() * imWidth, downFile.windowSizeWhenDropped.x() * imHeight} * display->get_dimension_scale(); + img.d.p1 = dropPos - imDim; + img.d.p2 = dropPos + imDim; + img.d.imageID = imageID; + downFile.comp->obj->send_comp_update(*this, true); + downFile.comp->obj->commit_update(*this); + } + return true; + } + case FileDownloader::DownloadData::Status::FAILURE: + Logger::get().log("WORLDFATAL", "Failed to download data from URL"); + components->erase(components, downFile.comp->obj.get_net_id()); + return true; + case FileDownloader::DownloadData::Status::IN_PROGRESS: + return false; + } + return false; + }); +} + +void DrawingProgram::add_file_to_canvas_by_path(const std::filesystem::path& 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::filesystem::path& filePath, Vector2f dropPos) { + NetworkingObjects::NetObjTemporaryPtr imageTempPtr = world.rMan.add_resource_file(filePath); + if(imageTempPtr) { + NetworkingObjects::NetObjID imageID = imageTempPtr.get_net_id(); + ResourceDisplay* display = world.rMan.get_display_data(imageID); + Vector2f imTrueDim = display->get_dimensions(); + CanvasComponentContainer* newContainer = new CanvasComponentContainer(world.netObjMan, CanvasComponentType::IMAGE); + ImageCanvasComponent& img = static_cast(newContainer->get_comp()); + newContainer->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; + components->push_back_and_send_create(components, newContainer); + } +} + +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(fileBuffer); + newResource.name = fileName; + NetworkingObjects::NetObjID imageID = world.rMan.add_resource(newResource).get_net_id(); + ResourceDisplay* display = world.rMan.get_display_data(imageID); + Vector2f imTrueDim = display->get_dimensions(); + CanvasComponentContainer* newContainer = new CanvasComponentContainer(world.netObjMan, CanvasComponentType::IMAGE); + ImageCanvasComponent& img = static_cast(newContainer->get_comp()); + newContainer->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; + components->push_back_and_send_create(components, newContainer); +} + void DrawingProgram::invalidate_cache_at_component(const CanvasComponentContainer::ObjInfoSharedPtr& objToCheck) { if(selection.is_selected(objToCheck)) selection.invalidate_cache_at_aabb_before_pos(objToCheck->obj->get_world_bounds(), objToCheck->pos); @@ -451,6 +586,20 @@ void DrawingProgram::draw(SkCanvas* canvas, const DrawData& drawData) { selection.draw_components(canvas, drawData); canvas->restore(); canvas->restore(); + + if(!drawData.main->takingScreenshot) { + for(auto& droppedDownFile : droppedDownloadingFiles) + static_cast(droppedDownFile.comp->obj->get_comp()).draw_download_progress_bar(canvas, drawData, droppedDownFile.downData->progress); + + for(auto& c : updateableComponents) { + if(c->obj->get_comp().get_type() == CanvasComponentType::IMAGE) { + auto& img = static_cast(c->obj->get_comp()); + float progress = drawData.main->world->rMan.get_resource_retrieval_progress(img.d.imageID); + img.draw_download_progress_bar(canvas, drawData, progress); + } + } + } + selection.draw_gui(canvas, drawData); } diff --git a/src/DrawingProgram/DrawingProgram.hpp b/src/DrawingProgram/DrawingProgram.hpp index aaa4f10..53e598a 100644 --- a/src/DrawingProgram/DrawingProgram.hpp +++ b/src/DrawingProgram/DrawingProgram.hpp @@ -34,6 +34,9 @@ class DrawingProgram { void write_components_server(cereal::PortableBinaryOutputArchive& a); void read_components_client(cereal::PortableBinaryInputArchive& a); + void add_file_to_canvas_by_path(const std::filesystem::path& filePath, Vector2f dropPos, bool addInSameThread); + void add_file_to_canvas_by_data(const std::string& fileName, std::string_view fileBuffer, Vector2f dropPos); + std::unordered_set get_used_resources() const; void load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version); void save_file(cereal::PortableBinaryOutputArchive& a) const; @@ -57,6 +60,11 @@ class DrawingProgram { void modify_grid(const NetworkingObjects::NetObjWeakPtr& gridToModify); private: + void drag_drop_update(); + void add_file_to_canvas_by_path_execute(const std::filesystem::path& filePath, Vector2f dropPos); + void check_updateable_components(); + void update_downloading_dropped_files(); + std::optional badFrametimeTimePoint; std::optional unorderedObjectsExistTimePoint; @@ -76,6 +84,7 @@ class DrawingProgram { DrawingProgramSelection selection; std::unique_ptr toolToSwitchToAfterUpdate; + std::unordered_set updateableComponents; bool temporaryEraser = false; bool temporaryPan = false; @@ -99,6 +108,13 @@ class DrawingProgram { int colorEditing = 0; } controls; + struct DroppedDownloadingFile { + CanvasComponentContainer::ObjInfoSharedPtr comp; + Vector2f windowSizeWhenDropped; + std::shared_ptr downData; + }; + std::vector droppedDownloadingFiles; + std::unique_ptr drawTool; uint32_t nextID = 0; diff --git a/src/ResourceManager.cpp b/src/ResourceManager.cpp index 8fecc08..1c24c04 100644 --- a/src/ResourceManager.cpp +++ b/src/ResourceManager.cpp @@ -84,12 +84,12 @@ void ResourceManager::update() { v->update(world); } -NetworkingObjects::NetObjID ResourceManager::add_resource_file(const std::filesystem::path& filePath) { +NetworkingObjects::NetObjTemporaryPtr ResourceManager::add_resource_file(const std::filesystem::path& filePath) { // https://nullptr.org/cpp-read-file-into-string/ std::ifstream file(filePath, std::ios::in | std::ios::binary | std::ios::ate); if(!file.is_open()) { Logger::get().log("INFO", "[ResourceManager::add_resource_file] Could not open file " + filePath.string()); - return {0, 0}; + return {}; } size_t resourcesize; @@ -98,7 +98,7 @@ NetworkingObjects::NetObjID ResourceManager::add_resource_file(const std::filesy if(tellgResult == -1) { Logger::get().log("INFO", "[ResourceManager::add_resource_file] tellg failed for file " + filePath.string()); - return {0, 0}; + return {}; } resourcesize = static_cast(tellgResult); @@ -121,13 +121,13 @@ NetworkingObjects::NetObjID ResourceManager::add_resource_file(const std::filesy return add_resource(resource); } -NetworkingObjects::NetObjID ResourceManager::add_resource(const ResourceData& resource) { +const NetworkingObjects::NetObjOwnerPtr& ResourceManager::add_resource(const ResourceData& resource) { auto it = std::find_if(resourceList.begin(), resourceList.end(), [&](const auto& p) { return p->data == resource.data || (*p->data) == (*resource.data); }); if(it != resourceList.end()) { Logger::get().log("INFO", "[ResourceManager::add_resource] File " + std::string(resource.name) + " is a duplicate"); - return it->get_net_id(); + return *it; } auto& resourceInsert = resourceList.emplace_back(world.netObjMan.make_obj_direct(resource)); if(world.con.localServer) { @@ -138,7 +138,7 @@ NetworkingObjects::NetObjID ResourceManager::add_resource(const ResourceData& re world.con.client_send_items_to_server(RESOURCE_COMMAND_CHANNEL, SERVER_NEW_RESOURCE_ID, resourceInsert.get_net_id()); world.con.client_send_items_to_server(RESOURCE_COMMAND_CHANNEL, SERVER_NEW_RESOURCE_DATA, resource); } - return resourceInsert.get_net_id(); + return resourceInsert; } ResourceDisplay* ResourceManager::get_display_data(const NetworkingObjects::NetObjID& fileID) { diff --git a/src/ResourceManager.hpp b/src/ResourceManager.hpp index 56a4d78..33cd949 100644 --- a/src/ResourceManager.hpp +++ b/src/ResourceManager.hpp @@ -26,8 +26,8 @@ class ResourceManager { void update(); void init_client_callbacks(); void init_server_callbacks(); - NetworkingObjects::NetObjID add_resource_file(const std::filesystem::path& filePath); - NetworkingObjects::NetObjID add_resource(const ResourceData& resource); + NetworkingObjects::NetObjTemporaryPtr add_resource_file(const std::filesystem::path& filePath); + const NetworkingObjects::NetObjOwnerPtr& add_resource(const ResourceData& resource); ResourceDisplay* get_display_data(const NetworkingObjects::NetObjID& fileID); const std::vector>& resource_list(); float get_resource_retrieval_progress(const NetworkingObjects::NetObjID& id); diff --git a/src/Toolbar.cpp b/src/Toolbar.cpp index 0f26bd3..7c6f206 100644 --- a/src/Toolbar.cpp +++ b/src/Toolbar.cpp @@ -540,9 +540,8 @@ void Toolbar::top_toolbar() { #else open_file_selector("Open File", {{"Any File", "*"}}, [w = make_weak_ptr(main.world)](const std::filesystem::path& p, const auto& e) { auto wLock = w.lock(); - // NOTE: Implement again later - //if(wLock) - // wLock->drawProg.add_file_to_canvas_by_path(p.string(), wLock->main.window.size.cast() / 2.0f, false); + if(wLock) + wLock->drawProg.add_file_to_canvas_by_path(p.string(), wLock->main.window.size.cast() / 2.0f, false); }); #endif }