2025-07-06 20:43:53 +03:00
# include "DrawingProgram.hpp"
2025-08-02 20:10:33 +03:00
# include "DrawingProgramToolBase.hpp"
2025-07-06 20:43:53 +03:00
# include <include/core/SkPaint.h>
# include <include/core/SkVertices.h>
# include "../DrawCamera.hpp"
# include "EllipseDrawTool.hpp"
2025-08-05 12:11:50 +03:00
# include "EyeDropperTool.hpp"
2025-09-10 15:03:06 +03:00
# include "GridModifyTool.hpp"
2025-07-06 20:43:53 +03:00
# include "../InputManager.hpp"
# include "../SharedTypes.hpp"
2025-08-02 20:10:33 +03:00
# include <memory>
2025-07-06 20:43:53 +03:00
# include <optional>
# include <Helpers/ConvertVec.hpp>
# include <cereal/types/vector.hpp>
# include <include/core/SkImage.h>
# include <include/core/SkSurface.h>
# include "../World.hpp"
# include "../MainProgram.hpp"
2025-12-14 18:57:40 +03:00
# include <Helpers/FileDownloader.hpp>
# include <Helpers/NetworkingObjects/NetObjID.hpp>
# include <Helpers/StringHelpers.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>
2025-07-26 21:20:14 +03:00
# 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-12-16 12:54:12 +03:00
# include "../CanvasComponents/ImageCanvasComponent.hpp"
2025-07-19 19:41:25 +03:00
2025-08-02 20:10:33 +03:00
# include "EraserTool.hpp"
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-24 16:35:48 +03:00
selection ( * this )
2025-07-06 20:43:53 +03:00
{
2025-08-02 20:39:21 +03:00
drawTool = DrawingProgramToolBase : : allocate_tool_type ( * this , DrawingProgramToolType : : BRUSH ) ;
2025-12-14 18:57:40 +03:00
}
2025-08-02 20:10:33 +03:00
2025-12-14 18:57:40 +03:00
void DrawingProgram : : init ( ) {
if ( world . netObjMan . is_server ( ) ) {
components = world . netObjMan . make_obj < NetworkingObjects : : NetObjOrderedList < CanvasComponentContainer > > ( ) ;
set_component_list_callbacks ( ) ;
}
2025-07-06 20:43:53 +03:00
}
2025-12-14 18:57:40 +03:00
void DrawingProgram : : set_component_list_callbacks ( ) {
components - > set_insert_callback ( [ & ] ( const CanvasComponentContainer : : ObjInfoSharedPtr & c ) {
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 ) ;
2025-12-16 12:54:12 +03:00
if ( c - > obj - > get_comp ( ) . get_type ( ) = = CanvasComponentType : : IMAGE )
updateableComponents . emplace ( c ) ;
2025-07-06 20:43:53 +03:00
} ) ;
2025-12-14 18:57:40 +03:00
components - > set_erase_callback ( [ & ] ( const CanvasComponentContainer : : ObjInfoSharedPtr & c ) {
compCache . erase_component ( c ) ;
selection . erase_component ( c ) ;
2025-12-15 15:36:42 +03:00
drawTool - > erase_component ( c ) ;
2025-12-16 12:54:12 +03:00
std : : erase_if ( droppedDownloadingFiles , [ & c ] ( auto & downloadingFile ) {
return downloadingFile . comp = = c ;
} ) ;
updateableComponents . erase ( c ) ;
2025-07-26 21:20:14 +03:00
} ) ;
2025-12-14 18:57:40 +03:00
components - > set_post_sync_callback ( [ & ] ( ) {
clear_draw_cache ( ) ;
2025-07-06 20:43:53 +03:00
} ) ;
}
2025-12-14 18:57:40 +03:00
void DrawingProgram : : scale_up ( const WorldScalar & scaleUpAmount ) {
//for(auto& c : components->get_data())
// c->obj->scale_up(scaleUpAmount);
//selection.deselect_all();
//force_rebuild_cache();
//switch_to_tool(drawTool->get_type() == DrawingProgramToolType::GRIDMODIFY ? DrawingProgramToolType::EDIT : drawTool->get_type(), true);
2025-07-21 13:57:48 +03:00
}
2025-12-14 18:57:40 +03:00
void DrawingProgram : : parallel_loop_all_components ( std : : function < void ( const CanvasComponentContainer : : ObjInfoSharedPtr & ) > func ) {
parallel_loop_container ( components - > get_data ( ) , func ) ;
2025-07-06 20:43:53 +03:00
}
2025-12-14 18:57:40 +03:00
void DrawingProgram : : erase_component_set ( const std : : unordered_set < CanvasComponentContainer : : ObjInfoSharedPtr > & compsToErase ) {
std : : unordered_set < NetworkingObjects : : NetObjID > idsToErase ;
for ( auto & c : compsToErase )
idsToErase . emplace ( c - > obj . get_net_id ( ) ) ;
components - > erase_unordered_set ( components , idsToErase ) ;
}
2025-08-18 08:16:29 +03:00
void DrawingProgram : : clear_draw_cache ( ) {
compCache . clear_own_cached_surfaces ( ) ;
selection . clear_own_cached_surfaces ( ) ;
}
2025-12-15 16:49:51 +03:00
void DrawingProgram : : write_components_server ( cereal : : PortableBinaryOutputArchive & a ) {
components . write_create_message ( a ) ;
}
void DrawingProgram : : read_components_client ( cereal : : PortableBinaryInputArchive & a ) {
components = world . netObjMan . read_create_message < NetworkingObjects : : NetObjOrderedList < CanvasComponentContainer > > ( a , nullptr ) ;
parallel_loop_all_components ( [ & ] ( const auto & c ) {
c - > obj - > commit_update_dont_invalidate_cache ( * this ) ;
} ) ;
compCache . test_rebuild ( components - > get_data ( ) , true ) ;
set_component_list_callbacks ( ) ;
}
2025-07-06 20:43:53 +03:00
void DrawingProgram : : toolbar_gui ( ) {
Toolbar & t = world . main . toolbar ;
2025-12-01 17:33:14 +03:00
t . gui . push_id ( " Drawing Program Toolbar GUI " ) ;
2025-12-02 16:09:30 +03:00
CLAY_AUTO_ID ( {
2025-07-06 20:43:53 +03:00
. layout = {
. sizing = { . width = CLAY_SIZING_FIT ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
2025-11-01 20:47:34 +03:00
. padding = CLAY_PADDING_ALL ( static_cast < uint16_t > ( t . io - > theme - > padding1 / 2 ) ) ,
2025-11-01 18:07:39 +03:00
. childGap = static_cast < uint16_t > ( t . io - > theme - > childGap1 / 2 ) ,
2025-07-06 20:43:53 +03:00
. 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 ) ,
2025-11-01 20:47:34 +03:00
. cornerRadius = CLAY_CORNER_RADIUS ( t . io - > theme - > windowCorners1 )
2025-07-06 20:43:53 +03:00
} ) {
t . gui . obstructing_window ( ) ;
2025-11-01 16:07:19 +03:00
if ( t . gui . svg_icon_button_transparent ( " Brush Toolbar Button " , " data/icons/brush.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : BRUSH ) ) { switch_to_tool ( DrawingProgramToolType : : BRUSH ) ; }
if ( t . gui . svg_icon_button_transparent ( " Eraser Toolbar Button " , " data/icons/eraser.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : ERASER ) ) { switch_to_tool ( DrawingProgramToolType : : ERASER ) ; }
if ( t . gui . svg_icon_button_transparent ( " Text Toolbar Button " , " data/icons/text.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : TEXTBOX ) ) { switch_to_tool ( DrawingProgramToolType : : TEXTBOX ) ; }
if ( t . gui . svg_icon_button_transparent ( " Ellipse Toolbar Button " , " data/icons/circle.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : ELLIPSE ) ) { switch_to_tool ( DrawingProgramToolType : : ELLIPSE ) ; }
if ( t . gui . svg_icon_button_transparent ( " Rect Toolbar Button " , " data/icons/rectangle.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : RECTANGLE ) ) { switch_to_tool ( DrawingProgramToolType : : RECTANGLE ) ; }
if ( t . gui . svg_icon_button_transparent ( " RectSelect Toolbar Button " , " data/icons/rectselect.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : RECTSELECT ) ) { switch_to_tool ( DrawingProgramToolType : : RECTSELECT ) ; }
if ( t . gui . svg_icon_button_transparent ( " LassoSelect Toolbar Button " , " data/icons/lassoselect.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : LASSOSELECT ) ) { switch_to_tool ( DrawingProgramToolType : : LASSOSELECT ) ; }
if ( t . gui . svg_icon_button_transparent ( " Edit Toolbar Button " , " data/icons/cursor.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : EDIT ) ) { switch_to_tool ( DrawingProgramToolType : : EDIT ) ; }
if ( t . gui . svg_icon_button_transparent ( " Eyedropper Toolbar Button " , " data/icons/eyedropper.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : EYEDROPPER ) ) { switch_to_tool ( DrawingProgramToolType : : EYEDROPPER ) ; }
if ( t . gui . svg_icon_button_transparent ( " Zoom Canvas Toolbar Button " , " data/icons/zoom.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : ZOOM ) ) { switch_to_tool ( DrawingProgramToolType : : ZOOM ) ; }
if ( t . gui . svg_icon_button_transparent ( " Pan Canvas Toolbar Button " , " data/icons/hand.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : PAN ) ) { switch_to_tool ( DrawingProgramToolType : : PAN ) ; }
2025-09-28 16:32:57 +03:00
double newRotationAngle = world . drawData . cam . c . rotation , oldRotationAngle = world . drawData . cam . c . rotation ;
t . gui . rotate_wheel ( " Canvas Rotate Wheel " , & newRotationAngle ) ;
if ( newRotationAngle ! = oldRotationAngle )
world . drawData . cam . c . rotate_about ( world . drawData . cam . c . from_space ( world . main . window . size . cast < float > ( ) * 0.5f ) , newRotationAngle - oldRotationAngle ) ;
2025-11-01 16:07:19 +03:00
if ( t . gui . big_color_button ( " Foreground Color " , & controls . foregroundColor , & controls . foregroundColor = = t . colorLeft ) )
t . color_selector_left ( & controls . foregroundColor = = t . colorLeft ? nullptr : & controls . foregroundColor ) ;
if ( t . gui . big_color_button ( " Background Color " , & controls . backgroundColor , & controls . backgroundColor = = t . colorLeft ) )
t . color_selector_left ( & controls . backgroundColor = = t . colorLeft ? nullptr : & controls . backgroundColor ) ;
2025-07-06 20:43:53 +03:00
}
2025-12-01 17:33:14 +03:00
t . gui . pop_id ( ) ;
}
2025-12-01 18:12:22 +03:00
// Should only be used in selection allowing tools
2025-12-01 17:33:14 +03:00
bool DrawingProgram : : selection_action_menu ( Vector2f popupPos ) {
Toolbar & t = world . main . toolbar ;
2025-12-01 18:12:22 +03:00
bool shouldClose = false ;
t . gui . list_popup_menu ( " Selection popup menu " , popupPos , [ & ] ( ) {
t . gui . text_label_light ( " Selection menu " ) ;
if ( t . gui . text_button_left_transparent ( " Paste " , " Paste " ) ) {
selection . deselect_all ( ) ;
2025-12-03 09:55:17 +03:00
selection . paste_clipboard ( popupPos * t . final_gui_scale ( ) ) ;
2025-12-01 18:12:22 +03:00
shouldClose = true ;
}
if ( selection . is_something_selected ( ) ) {
if ( t . gui . text_button_left_transparent ( " Copy " , " Copy " ) ) {
selection . selection_to_clipboard ( ) ;
shouldClose = true ;
}
if ( t . gui . text_button_left_transparent ( " Cut " , " Cut " ) ) {
selection . selection_to_clipboard ( ) ;
selection . delete_all ( ) ;
shouldClose = true ;
}
if ( t . gui . text_button_left_transparent ( " Delete " , " Delete " ) ) {
selection . delete_all ( ) ;
shouldClose = true ;
}
}
2025-12-01 17:33:14 +03:00
} ) ;
2025-12-01 18:12:22 +03:00
return ! shouldClose ;
2025-12-01 17:33:14 +03:00
}
bool DrawingProgram : : right_click_popup_gui ( Vector2f popupPos ) {
Toolbar & t = world . main . toolbar ;
t . gui . push_id ( " Drawing Program right click GUI " ) ;
bool toRet = drawTool - > right_click_popup_gui ( popupPos ) ;
t . gui . pop_id ( ) ;
return toRet ;
2025-07-06 20:43:53 +03:00
}
void DrawingProgram : : tool_options_gui ( ) {
Toolbar & t = world . main . toolbar ;
2025-08-02 20:39:21 +03:00
float minGUIWidth = drawTool - > get_type ( ) = = DrawingProgramToolType : : SCREENSHOT ? 300 : 200 ;
2025-12-02 16:09:30 +03:00
CLAY_AUTO_ID ( {
2025-07-06 20:43:53 +03:00
. 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 ) ,
2025-11-01 20:47:34 +03:00
. cornerRadius = CLAY_CORNER_RADIUS ( t . io - > theme - > windowCorners1 )
2025-07-06 20:43:53 +03:00
} ) {
t . gui . obstructing_window ( ) ;
2025-08-02 20:10:33 +03:00
drawTool - > gui_toolbox ( ) ;
2025-07-06 20:43:53 +03:00
}
}
2025-12-11 16:05:41 +03:00
void DrawingProgram : : modify_grid ( const NetworkingObjects : : NetObjWeakPtr < WorldGrid > & gridToModify ) {
2025-09-24 16:18:20 +03:00
std : : unique_ptr < GridModifyTool > newTool ( std : : make_unique < GridModifyTool > ( * this ) ) ;
2025-12-10 13:06:45 +03:00
newTool - > set_grid ( gridToModify ) ;
2025-09-24 16:18:20 +03:00
switch_to_tool_ptr ( std : : move ( newTool ) ) ;
2025-09-12 09:54:49 +03:00
}
2025-07-06 20:43:53 +03:00
void DrawingProgram : : update ( ) {
2025-08-26 12:03:09 +03:00
if ( world . main . window . lastFrameTime > std : : chrono : : milliseconds ( 33 ) ) { // Around 30FPS
if ( ! badFrametimeTimePoint )
badFrametimeTimePoint = std : : chrono : : steady_clock : : now ( ) ;
else if ( unorderedObjectsExistTimePoint & & std : : chrono : : steady_clock : : now ( ) - badFrametimeTimePoint . value ( ) > = std : : chrono : : seconds ( 5 ) & & std : : chrono : : steady_clock : : now ( ) - unorderedObjectsExistTimePoint . value ( ) > = std : : chrono : : seconds ( 5 ) ) {
force_rebuild_cache ( ) ;
unorderedObjectsExistTimePoint = std : : nullopt ;
badFrametimeTimePoint = std : : nullopt ;
}
}
else
badFrametimeTimePoint = std : : nullopt ;
if ( compCache . get_unsorted_component_list ( ) . empty ( ) ) {
unorderedObjectsExistTimePoint = std : : nullopt ;
}
else if ( ! unorderedObjectsExistTimePoint ) {
unorderedObjectsExistTimePoint = std : : chrono : : steady_clock : : now ( ) ;
}
2025-09-28 18:25:46 +03:00
if ( world . main . input . pen . isEraser & & ! temporaryEraser ) {
if ( drawTool - > get_type ( ) = = DrawingProgramToolType : : BRUSH )
switch_to_tool ( DrawingProgramToolType : : ERASER ) ;
2025-07-06 20:43:53 +03:00
temporaryEraser = true ;
}
2025-09-28 18:25:46 +03:00
else if ( ! world . main . input . pen . isEraser & & temporaryEraser ) {
if ( drawTool - > get_type ( ) = = DrawingProgramToolType : : ERASER )
switch_to_tool ( DrawingProgramToolType : : BRUSH ) ;
2025-07-06 20:43:53 +03:00
temporaryEraser = false ;
}
2025-09-28 18:25:46 +03:00
if ( world . main . input . key ( InputManager : : KEY_HOLD_TO_PAN ) . held & & ! temporaryPan ) {
toolTypeAfterTempPan = drawTool - > get_type ( ) ;
switch_to_tool ( DrawingProgramToolType : : PAN ) ;
temporaryPan = true ;
}
if ( ! world . main . input . key ( InputManager : : KEY_HOLD_TO_PAN ) . held & & temporaryPan ) {
switch_to_tool ( toolTypeAfterTempPan ) ;
temporaryPan = false ;
}
2025-07-06 20:43:53 +03:00
controls . cursorHoveringOverCanvas = ! world . main . toolbar . io - > hoverObstructed ;
controls . leftClick = controls . cursorHoveringOverCanvas & & world . main . input . mouse . leftClicks ;
if ( controls . leftClick ) {
controls . leftClickHeld = true ;
2025-12-01 17:33:14 +03:00
world . main . toolbar . rightClickPopupLocation = std : : nullopt ;
2025-07-06 20:43:53 +03:00
}
if ( controls . leftClickHeld & & ! world . main . input . mouse . leftDown ) {
controls . leftClick = false ;
controls . leftClickHeld = false ;
controls . leftClickReleased = true ;
}
2025-09-28 11:59:08 +03:00
controls . middleClick = controls . cursorHoveringOverCanvas & & ( world . main . input . mouse . middleClicks | | world . main . input . pen . buttons [ world . main . toolbar . tabletOptions . middleClickButton ] . pressed ) ;
2025-09-28 20:13:54 +03:00
bool middleHeld = world . main . input . mouse . middleDown | | world . main . input . pen . buttons [ world . main . toolbar . tabletOptions . middleClickButton ] . held ;
2025-09-28 11:59:08 +03:00
if ( controls . middleClick ) {
controls . middleClickHeld = true ;
2025-12-01 17:33:14 +03:00
world . main . toolbar . rightClickPopupLocation = std : : nullopt ;
2025-09-28 11:59:08 +03:00
}
2025-09-28 20:13:54 +03:00
if ( controls . middleClickHeld & & ! middleHeld ) {
2025-09-28 11:59:08 +03:00
controls . middleClick = false ;
controls . middleClickHeld = false ;
controls . middleClickReleased = true ;
}
2025-07-06 20:43:53 +03:00
if ( controls . cursorHoveringOverCanvas & & ( world . main . input . mouse . rightClicks | | world . main . input . pen . buttons [ world . main . toolbar . tabletOptions . rightClickButton ] . pressed ) ) {
2025-12-01 17:33:14 +03:00
if ( world . main . toolbar . rightClickPopupLocation )
world . main . toolbar . rightClickPopupLocation = std : : nullopt ;
2025-07-06 20:43:53 +03:00
else
2025-12-01 17:33:14 +03:00
world . main . toolbar . rightClickPopupLocation = world . main . input . mouse . pos / world . main . toolbar . final_gui_scale ( ) ;
2025-07-06 20:43:53 +03:00
}
controls . previousMouseWorldPos = controls . currentMouseWorldPos ;
controls . currentMouseWorldPos = world . get_mouse_world_pos ( ) ;
2025-12-16 12:54:12 +03:00
if ( addFileInNextFrame ) {
add_file_to_canvas_by_path_execute ( addFileInfo . first , addFileInfo . second ) ;
addFileInNextFrame = false ;
}
drag_drop_update ( ) ;
2025-07-06 20:43:53 +03:00
if ( world . main . input . key ( InputManager : : KEY_DRAW_TOOL_BRUSH ) . pressed )
2025-08-02 20:39:21 +03:00
switch_to_tool ( DrawingProgramToolType : : BRUSH ) ;
2025-07-06 20:43:53 +03:00
else if ( world . main . input . key ( InputManager : : KEY_DRAW_TOOL_ERASER ) . pressed )
2025-08-02 20:39:21 +03:00
switch_to_tool ( DrawingProgramToolType : : ERASER ) ;
2025-07-06 20:43:53 +03:00
else if ( world . main . input . key ( InputManager : : KEY_DRAW_TOOL_RECTSELECT ) . pressed )
2025-08-02 20:39:21 +03:00
switch_to_tool ( DrawingProgramToolType : : RECTSELECT ) ;
2025-07-06 20:43:53 +03:00
else if ( world . main . input . key ( InputManager : : KEY_DRAW_TOOL_RECTANGLE ) . pressed )
2025-08-02 20:39:21 +03:00
switch_to_tool ( DrawingProgramToolType : : RECTANGLE ) ;
2025-07-06 20:43:53 +03:00
else if ( world . main . input . key ( InputManager : : KEY_DRAW_TOOL_ELLIPSE ) . pressed )
2025-08-02 20:39:21 +03:00
switch_to_tool ( DrawingProgramToolType : : ELLIPSE ) ;
2025-07-06 20:43:53 +03:00
else if ( world . main . input . key ( InputManager : : KEY_DRAW_TOOL_TEXTBOX ) . pressed )
2025-08-02 20:39:21 +03:00
switch_to_tool ( DrawingProgramToolType : : TEXTBOX ) ;
2025-08-05 12:11:50 +03:00
else if ( world . main . input . key ( InputManager : : KEY_DRAW_TOOL_EYEDROPPER ) . pressed )
switch_to_tool ( DrawingProgramToolType : : EYEDROPPER ) ;
2025-07-06 20:43:53 +03:00
else if ( world . main . input . key ( InputManager : : KEY_DRAW_TOOL_SCREENSHOT ) . pressed )
2025-08-02 20:39:21 +03:00
switch_to_tool ( DrawingProgramToolType : : SCREENSHOT ) ;
2025-12-10 21:46:08 +05:30
else if ( world . main . input . key ( InputManager : : KEY_DRAW_TOOL_ZOOM ) . pressed )
switch_to_tool ( DrawingProgramToolType : : ZOOM ) ;
else if ( world . main . input . key ( InputManager : : KEY_DRAW_TOOL_LASSOSELECT ) . pressed )
switch_to_tool ( DrawingProgramToolType : : LASSOSELECT ) ;
else if ( world . main . input . key ( InputManager : : KEY_DRAW_TOOL_PAN ) . pressed )
switch_to_tool ( DrawingProgramToolType : : PAN ) ;
2025-08-02 20:10:33 +03:00
2025-08-05 19:04:47 +03:00
selection . update ( ) ;
2025-08-02 20:10:33 +03:00
drawTool - > tool_update ( ) ;
2025-07-06 20:43:53 +03:00
2025-12-15 15:36:42 +03:00
// Switch tools after the tool update, not before, so that we dont have to call erase_component on the toolToSwitchToAfterUpdate as well (components will not be erased in this time period)
2025-10-11 15:35:17 +03:00
if ( toolToSwitchToAfterUpdate ) {
switch_to_tool_ptr ( std : : move ( toolToSwitchToAfterUpdate ) ) ;
toolToSwitchToAfterUpdate = nullptr ;
}
2025-07-06 20:43:53 +03:00
if ( controls . leftClickReleased )
controls . leftClickReleased = false ;
2025-12-16 12:54:12 +03:00
update_downloading_dropped_files ( ) ;
check_updateable_components ( ) ;
2025-08-02 20:39:21 +03:00
if ( drawTool - > get_type ( ) = = DrawingProgramToolType : : ERASER ) {
2025-08-02 20:10:33 +03:00
EraserTool * eraserTool = static_cast < EraserTool * > ( drawTool . get ( ) ) ;
2025-12-14 18:57:40 +03:00
compCache . test_rebuild_dont_include_set_dont_include_nodes ( components - > get_data ( ) , eraserTool - > erasedComponents , eraserTool - > erasedBVHNodes ) ;
2025-08-02 20:10:33 +03:00
}
2025-10-03 13:16:38 +03:00
else if ( is_selection_allowing_tool ( drawTool - > get_type ( ) ) )
2025-12-14 18:57:40 +03:00
compCache . test_rebuild_dont_include_set ( components - > get_data ( ) , selection . get_selected_set ( ) ) ;
2025-07-26 21:20:14 +03:00
else
2025-12-14 18:57:40 +03:00
compCache . test_rebuild ( components - > get_data ( ) ) ;
2025-07-26 21:20:14 +03:00
}
2025-12-16 12:54:12 +03:00
void DrawingProgram : : check_updateable_components ( ) {
for ( auto & comp : updateableComponents )
comp - > obj - > get_comp ( ) . update ( * this ) ;
}
2025-10-03 13:16:38 +03:00
bool DrawingProgram : : is_actual_selection_tool ( DrawingProgramToolType typeToCheck ) {
2025-09-29 15:15:16 +03:00
return typeToCheck = = DrawingProgramToolType : : RECTSELECT | | typeToCheck = = DrawingProgramToolType : : LASSOSELECT | | typeToCheck = = DrawingProgramToolType : : EDIT ;
}
2025-10-03 13:16:38 +03:00
bool DrawingProgram : : is_selection_allowing_tool ( DrawingProgramToolType typeToCheck ) {
return is_actual_selection_tool ( typeToCheck ) | | typeToCheck = = DrawingProgramToolType : : PAN | | typeToCheck = = DrawingProgramToolType : : ZOOM ;
}
2025-08-02 21:14:57 +03:00
void DrawingProgram : : switch_to_tool_ptr ( std : : unique_ptr < DrawingProgramToolBase > newTool ) {
drawTool - > switch_tool ( newTool - > get_type ( ) ) ;
drawTool = std : : move ( newTool ) ;
2025-12-01 17:33:14 +03:00
world . main . toolbar . rightClickPopupLocation = std : : nullopt ;
2025-08-02 21:14:57 +03:00
}
2025-09-29 15:15:16 +03:00
void DrawingProgram : : switch_to_tool ( DrawingProgramToolType newToolType , bool force ) {
if ( newToolType ! = drawTool - > get_type ( ) | | force ) {
2025-08-05 19:04:47 +03:00
drawTool - > switch_tool ( newToolType ) ;
2025-08-02 20:39:21 +03:00
drawTool = DrawingProgramToolBase : : allocate_tool_type ( * this , newToolType ) ;
2025-12-01 17:33:14 +03:00
world . main . toolbar . rightClickPopupLocation = std : : nullopt ;
2025-08-02 20:39:21 +03:00
}
}
2025-07-27 16:53:22 +03:00
bool DrawingProgram : : prevent_undo_or_redo ( ) {
2025-08-02 20:10:33 +03:00
return drawTool - > prevent_undo_or_redo ( ) ;
2025-07-27 16:53:22 +03:00
}
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 ( ) ;
2025-10-12 18:24:59 +03:00
sk_sp < SkPathEffect > lassoLineDashEffect = SkDashPathEffect : : Make ( { intervals , 2 } , - std : : fmod ( timeSinceEpoch * 50 , 15 ) ) ;
2025-08-02 12:31:16 +03:00
SkPaint selectLinePaint ;
selectLinePaint . setStyle ( SkPaint : : kStroke_Style ) ;
selectLinePaint . setStrokeWidth ( 3 ) ;
2025-12-16 23:06:46 +03:00
selectLinePaint . setColor4f ( world . canvasTheme . get_tool_front_color ( ) ) ;
2025-08-02 12:31:16 +03:00
selectLinePaint . setPathEffect ( lassoLineDashEffect ) ;
return selectLinePaint ;
}
2025-07-26 21:20:14 +03:00
void DrawingProgram : : force_rebuild_cache ( ) {
2025-08-02 20:39:21 +03:00
if ( drawTool - > get_type ( ) = = DrawingProgramToolType : : ERASER ) {
2025-08-02 20:10:33 +03:00
EraserTool * eraserTool = static_cast < EraserTool * > ( drawTool . get ( ) ) ;
2025-12-14 18:57:40 +03:00
compCache . test_rebuild_dont_include_set_dont_include_nodes ( components - > get_data ( ) , eraserTool - > erasedComponents , eraserTool - > erasedBVHNodes , true ) ;
2025-08-02 20:10:33 +03:00
}
2025-10-03 13:16:38 +03:00
else if ( is_selection_allowing_tool ( drawTool - > get_type ( ) ) )
2025-12-14 18:57:40 +03:00
compCache . test_rebuild_dont_include_set ( components - > get_data ( ) , selection . get_selected_set ( ) , true ) ;
2025-07-26 21:20:14 +03:00
else
2025-12-14 18:57:40 +03:00
compCache . test_rebuild ( components - > get_data ( ) , true ) ;
2025-11-16 16:39:12 +03:00
}
2025-12-16 12:54:12 +03:00
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 < ImageCanvasComponent & > ( 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 < float > ( ) , 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 < ImageCanvasComponent & > ( downFile . comp - > obj - > get_comp ( ) ) ;
Vector2f dropPos = ( img . d . p1 + img . d . p2 ) * 0.5f ;
ResourceData newResource ;
newResource . data = std : : make_shared < std : : string > ( 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 < ResourceData > 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 < ImageCanvasComponent & > ( 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 < std : : string > ( 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 < ImageCanvasComponent & > ( 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 ) ;
}
2025-12-14 18:57:40 +03:00
void DrawingProgram : : invalidate_cache_at_component ( const CanvasComponentContainer : : ObjInfoSharedPtr & objToCheck ) {
2025-07-31 19:28:18 +03:00
if ( selection . is_selected ( objToCheck ) )
2025-12-14 18:57:40 +03:00
selection . invalidate_cache_at_aabb_before_pos ( objToCheck - > obj - > get_world_bounds ( ) , objToCheck - > pos ) ;
2025-07-31 19:28:18 +03:00
else
2025-12-14 18:57:40 +03:00
compCache . invalidate_cache_at_aabb_before_pos ( objToCheck - > obj - > get_world_bounds ( ) , objToCheck - > pos ) ;
2025-07-31 19:28:18 +03:00
}
2025-12-14 18:57:40 +03:00
void DrawingProgram : : preupdate_component ( const CanvasComponentContainer : : ObjInfoSharedPtr & objToCheck ) {
2025-07-31 19:28:18 +03:00
if ( selection . is_selected ( objToCheck ) )
selection . preupdate_component ( objToCheck ) ;
else
compCache . preupdate_component ( objToCheck ) ;
}
2025-11-23 15:02:02 +03:00
float DrawingProgram : : drag_point_radius ( ) {
return 8.0f * world . main . toolbar . final_gui_scale ( ) ;
}
2025-07-06 20:43:53 +03:00
void DrawingProgram : : draw_drag_circle ( SkCanvas * canvas , const Vector2f & sPos , const SkColor4f & c , const DrawData & drawData , float radiusMultiplier ) {
2025-11-23 15:02:02 +03:00
float constantRadius = drag_point_radius ( ) * radiusMultiplier ;
2025-07-06 20:43:53 +03:00
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 ) ;
}
2025-12-14 18:57:40 +03:00
void DrawingProgram : : load_file ( cereal : : PortableBinaryInputArchive & a , VersionNumber version ) {
//uint64_t compCount;
//a(compCount);
//for(uint64_t i = 0; i < compCount; i++) {
// DrawComponentType t;
// a(t);
// auto newComp = DrawComponent::allocate_comp_type(t);
// a(newComp->id, newComp->coords);
// newComp->load_file(a, version);
// components.init_emplace_back(newComp);
//}
//parallel_loop_all_components([&](const auto& c){
// c->obj->commit_update(*this, false);
//});
//compCache.test_rebuild(components.client_list(), true);
}
2025-11-16 16:39:12 +03:00
void DrawingProgram : : save_file ( cereal : : PortableBinaryOutputArchive & a ) const {
2025-12-14 18:57:40 +03:00
//auto& cList = components->get_data();
//a((uint64_t)cList.size());
//for(size_t i = 0; i < cList.size(); i++) {
// a(cList[i]->obj->get_type(), cList[i]->obj->id, cList[i]->obj->coords);
// cList[i]->obj->save_file(a);
//}
2025-07-06 20:43:53 +03:00
}
void DrawingProgram : : draw ( SkCanvas * canvas , const DrawData & drawData ) {
if ( drawData . dontUseDrawProgCache ) {
2025-12-14 18:57:40 +03:00
for ( auto & c : components - > get_data ( ) )
2025-07-06 20:43:53 +03:00
c - > obj - > draw ( canvas , drawData ) ;
}
else {
2025-08-03 16:52:11 +03:00
canvas - > saveLayer ( nullptr , nullptr ) ;
canvas - > clear ( SkColor4f { 0.0f , 0.0f , 0.0f , 0.0f } ) ;
2025-07-24 16:35:48 +03:00
compCache . refresh_all_draw_cache ( drawData ) ;
2025-08-26 12:03:09 +03:00
compCache . draw_components_to_canvas ( canvas , drawData , { } ) ;
2025-08-03 16:52:11 +03:00
canvas - > saveLayer ( nullptr , nullptr ) ;
selection . draw_components ( canvas , drawData ) ;
canvas - > restore ( ) ;
canvas - > restore ( ) ;
2025-12-16 12:54:12 +03:00
if ( ! drawData . main - > takingScreenshot ) {
for ( auto & droppedDownFile : droppedDownloadingFiles )
static_cast < ImageCanvasComponent & > ( 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 < ImageCanvasComponent & > ( 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 ) ;
}
}
}
2025-08-03 16:52:11 +03:00
selection . draw_gui ( canvas , drawData ) ;
2025-07-06 20:43:53 +03:00
}
2025-08-02 20:10:33 +03:00
drawTool - > draw ( canvas , drawData ) ;
2025-07-06 20:43:53 +03:00
}
Vector4f * DrawingProgram : : get_foreground_color_ptr ( ) {
return & controls . foregroundColor ;
}