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>
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-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 ) ,
2025-07-24 16:35:48 +03:00
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 ;
} ;
2025-07-19 15:57:09 +03:00
components . clientInsertCallback = [ & ] ( const CollabListType : : ObjectInfoPtr & c ) {
2025-07-28 11:09:59 +03:00
if ( addToCompCacheOnInsert )
compCache . add_component ( c ) ;
2025-07-21 13:57:48 +03:00
DrawComponentType t = c - > obj - > get_type ( ) ;
if ( t = = DRAWCOMPONENT_IMAGE | | t = = DRAWCOMPONENT_TEXTBOX )
updateableComponents . emplace ( c - > obj ) ;
2025-07-19 15:57:09 +03:00
} ;
components . clientEraseCallback = [ & ] ( const CollabListType : : ObjectInfoPtr & c ) {
compCache . erase_component ( c ) ;
2025-07-27 16:30:45 +03:00
selection . erase_component ( c ) ;
2025-07-26 21:20:14 +03:00
delayedUpdateComponents . erase ( c - > obj ) ;
2025-07-21 13:57:48 +03:00
updateableComponents . erase ( c - > obj ) ;
2025-07-19 15:57:09 +03:00
} ;
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 ) ;
2025-07-26 21:20:14 +03:00
auto objPtr = components . get_item_by_id ( id ) ;
if ( ! objPtr )
2025-07-06 20:43:53 +03:00
return ;
2025-07-26 21:20:14 +03:00
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
}
} ) ;
2025-07-27 11:56:34 +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 ) ;
2025-07-27 11:56:34 +03:00
} ) ;
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-27 11:56:34 +03:00
}
}
2025-07-06 20:43:53 +03:00
} ) ;
2025-07-26 21:20:14 +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
} ) ;
2025-07-26 21:20:14 +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 )
2025-07-26 21:20:14 +03:00
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
} ) ;
}
2025-07-26 21:20:14 +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 ) ;
2025-07-21 13:57:48 +03:00
} ) ;
}
void DrawingProgram : : check_updateable_components ( ) {
for ( auto & comp : updateableComponents )
comp - > update ( * this ) ;
}
2025-07-19 15:57:09 +03:00
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 ( ) ;
2025-07-10 22:09:56 +03:00
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 ;
2025-07-26 21:20:14 +03:00
check_delayed_update_timers ( ) ;
2025-07-21 13:57:48 +03:00
check_updateable_components ( ) ;
2025-07-19 15:57:09 +03:00
2025-07-24 16:35:48 +03:00
selection . update ( ) ;
2025-07-26 21:20:14 +03:00
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 ( ) ) ;
}
2025-07-27 16:53:22 +03:00
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 ( ) ;
2025-07-27 16:53:22 +03:00
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 ;
}
2025-07-26 21:20:14 +03:00
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 ) {
2025-07-10 22:28:57 +03:00
if ( droppedItem . isFile & & std : : filesystem : : is_regular_file ( droppedItem . droppedData ) ) {
# ifdef __EMSCRIPTEN_
2025-07-10 22:09:56 +03:00
add_file_to_canvas_by_path ( droppedItem . droppedData , world . main . input . mouse . pos , true ) ;
2025-07-10 22:28:57 +03:00
# else
add_file_to_canvas_by_path ( droppedItem . droppedData , droppedItem . pos , true ) ;
# endif
}
2025-07-06 20:43:53 +03:00
}
}
}
2025-07-10 22:09:56 +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 ) ;
2025-07-10 22:09:56 +03:00
uint64_t placement = components . client_list ( ) . size ( ) ;
2025-07-27 16:30:45 +03:00
auto objAdd = components . client_insert ( placement , img ) ;
2025-07-21 12:26:07 +03:00
img - > client_send_place ( * this ) ;
2025-07-27 16:30:45 +03:00
add_undo_place_component ( objAdd ) ;
2025-07-10 22:09:56 +03:00
}
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 ) ;
2025-07-10 22:09:56 +03:00
uint64_t placement = components . client_list ( ) . size ( ) ;
2025-07-27 16:30:45 +03:00
auto objAdd = components . client_insert ( placement , img ) ;
2025-07-21 12:26:07 +03:00
img - > client_send_place ( * this ) ;
2025-07-27 16:30:45 +03:00
add_undo_place_component ( objAdd ) ;
2025-07-10 22:09:56 +03:00
}
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 ) ;
}
2025-07-21 18:05:41 +03:00
parallel_loop_all_components ( [ & ] ( const auto & c ) {
2025-07-31 09:34:17 +03:00
c - > obj - > commit_update ( * this , false ) ;
2025-07-21 18:05:41 +03:00
} ) ;
2025-07-26 21:20:14 +03:00
compCache . test_rebuild ( components . client_list ( ) , true ) ;
2025-07-06 20:43:53 +03:00
}
2025-07-27 20:04:26 +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 ;
}
} ) ;
}
2025-07-31 19:28:18 +03:00
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 ) {
2025-07-27 20:04:26 +03:00
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 ( ) ) ;
2025-07-27 20:04:26 +03:00
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 )
2025-07-27 20:04:26 +03:00
if ( ! comp - > obj - > collabListInfo . lock ( ) )
return false ;
std : : unordered_set < ServerClientID > idsToErase ;
2025-07-28 11:09:59 +03:00
for ( auto & c : objSetToUndo )
2025-07-27 20:04:26 +03:00
idsToErase . emplace ( c - > id ) ;
DrawComponent : : client_send_erase_set ( * this , idsToErase ) ;
2025-07-28 11:09:59 +03:00
components . client_erase_set ( objSetToUndo ) ;
2025-07-27 20:04:26 +03:00
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 ) {
2025-07-19 15:57:09 +03:00
parallel_loop_all_components ( [ & ] ( auto & c ) {
2025-07-06 20:43:53 +03:00
c - > obj - > calculate_draw_transform ( drawData ) ;
2025-07-19 15:57:09 +03:00
} ) ;
2025-07-31 15:30:22 +03:00
for ( auto & c : components . client_list ( ) ) {
2025-07-06 20:43:53 +03:00
c - > obj - > draw ( canvas , drawData ) ;
2025-07-31 15:30:22 +03:00
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 ) {
2025-07-19 15:57:09 +03:00
parallel_loop_all_components ( [ & ] ( auto & c ) {
2025-07-06 20:43:53 +03:00
c - > obj - > calculate_draw_transform ( drawData ) ;
2025-07-19 15:57:09 +03:00
} ) ;
2025-07-22 12:22:08 +03:00
for ( auto & c : components . client_list ( ) ) {
2025-07-20 15:24:44 +03:00
c - > obj - > draw ( canvas , drawData ) ;
2025-07-22 12:22:08 +03:00
c - > obj - > drawSetupData . shouldDraw = false ;
}
2025-07-19 15:57:09 +03:00
}
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 } ) ;
2025-07-24 16:35:48 +03:00
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-19 15:57:09 +03:00
}
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 ;
}