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 "../DrawComponents/DrawComponent.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"
# include "../Server/CommandList.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 "../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-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-06 20:43:53 +03:00
components ( [ & ] ( ) { return world . get_new_id ( ) ; } ) ,
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-08-02 20:10:33 +03:00
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-08-13 19:23:14 +03:00
components . clientServerFirstPosShiftCallback = [ & ] ( uint64_t firstShiftPos ) {
2025-08-18 08:16:29 +03:00
clear_draw_cache ( ) ;
2025-07-19 19:41:25 +03:00
} ;
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 ) ;
auto newObj = DrawComponent : : allocate_comp_type ( type ) ;
2025-08-27 11:38:36 +03:00
message ( newObj - > id , newObj - > coords , * newObj ) ;
bool didntExistPreviously = components . server_insert ( insertPosition , newObj ) ;
2025-07-06 20:43:53 +03:00
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 ;
}
2025-08-18 08:16:29 +03:00
void DrawingProgram : : clear_draw_cache ( ) {
compCache . clear_own_cached_surfaces ( ) ;
selection . clear_own_cached_surfaces ( ) ;
}
2025-07-06 20:43:53 +03:00
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 ( ) ;
2025-08-02 20:39:21 +03:00
if ( t . gui . svg_icon_button ( " Brush Toolbar Button " , " data/icons/brush.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : BRUSH ) ) { switch_to_tool ( DrawingProgramToolType : : BRUSH ) ; }
if ( t . gui . svg_icon_button ( " Eraser Toolbar Button " , " data/icons/eraser.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : ERASER ) ) { switch_to_tool ( DrawingProgramToolType : : ERASER ) ; }
if ( t . gui . svg_icon_button ( " Text Toolbar Button " , " data/icons/text.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : TEXTBOX ) ) { switch_to_tool ( DrawingProgramToolType : : TEXTBOX ) ; }
if ( t . gui . svg_icon_button ( " Ellipse Toolbar Button " , " data/icons/circle.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : ELLIPSE ) ) { switch_to_tool ( DrawingProgramToolType : : ELLIPSE ) ; }
if ( t . gui . svg_icon_button ( " Rect Toolbar Button " , " data/icons/rectangle.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : RECTANGLE ) ) { switch_to_tool ( DrawingProgramToolType : : RECTANGLE ) ; }
if ( t . gui . svg_icon_button ( " RectSelect Toolbar Button " , " data/icons/rectselect.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : RECTSELECT ) ) { switch_to_tool ( DrawingProgramToolType : : RECTSELECT ) ; }
2025-08-03 09:51:10 +03:00
if ( t . gui . svg_icon_button ( " LassoSelect Toolbar Button " , " data/icons/lassoselect.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : LASSOSELECT ) ) { switch_to_tool ( DrawingProgramToolType : : LASSOSELECT ) ; }
2025-08-02 20:39:21 +03:00
if ( t . gui . svg_icon_button ( " Edit Toolbar Button " , " data/icons/cursor.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : EDIT ) ) { switch_to_tool ( DrawingProgramToolType : : EDIT ) ; }
2025-08-05 12:11:50 +03:00
if ( t . gui . svg_icon_button ( " Eyedropper Toolbar Button " , " data/icons/eyedropper.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : EYEDROPPER ) ) { switch_to_tool ( DrawingProgramToolType : : EYEDROPPER ) ; }
2025-09-28 11:59:08 +03:00
if ( t . gui . svg_icon_button ( " Zoom Canvas Toolbar Button " , " data/icons/zoom.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : ZOOM ) ) { switch_to_tool ( DrawingProgramToolType : : ZOOM ) ; }
2025-09-28 15:00:19 +03:00
if ( t . gui . svg_icon_button ( " 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-07-06 20:43:53 +03:00
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 ;
2025-08-02 20:39:21 +03:00
float minGUIWidth = drawTool - > get_type ( ) = = DrawingProgramToolType : : SCREENSHOT ? 300 : 200 ;
2025-07-06 20:43:53 +03:00
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 ( ) ;
2025-08-02 20:10:33 +03:00
drawTool - > gui_toolbox ( ) ;
2025-07-06 20:43:53 +03:00
}
}
2025-09-24 13:32:50 +03:00
void DrawingProgram : : modify_grid ( ServerClientID gridToModifyID ) {
2025-09-24 16:18:20 +03:00
std : : unique_ptr < GridModifyTool > newTool ( std : : make_unique < GridModifyTool > ( * this ) ) ;
newTool - > set_grid_id ( gridToModifyID ) ;
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 ;
world . main . toolbar . paintPopupLocation = std : : nullopt ;
}
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 ;
world . main . toolbar . paintPopupLocation = std : : nullopt ;
}
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 ) ) {
if ( world . main . toolbar . paintPopupLocation )
world . main . toolbar . paintPopupLocation = std : : nullopt ;
else
2025-08-17 16:49:30 +03:00
world . main . toolbar . paintPopupLocation = 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-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 )
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-07-06 20:43:53 +03:00
else if ( world . main . input . key ( InputManager : : KEY_DRAW_TOOL_EDIT ) . pressed )
2025-08-02 20:39:21 +03:00
switch_to_tool ( DrawingProgramToolType : : EDIT ) ;
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
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-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 ( ) ) ;
compCache . test_rebuild_dont_include_set_dont_include_nodes ( components . client_list ( ) , eraserTool - > erasedComponents , eraserTool - > erasedBVHNodes ) ;
}
2025-08-02 20:39:21 +03:00
else if ( drawTool - > get_type ( ) = = DrawingProgramToolType : : RECTSELECT )
2025-07-26 21:20:14 +03:00
compCache . test_rebuild_dont_include_set ( components . client_list ( ) , selection . get_selected_set ( ) ) ;
else
compCache . test_rebuild ( components . client_list ( ) ) ;
}
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-08-02 20:39:21 +03:00
void DrawingProgram : : switch_to_tool ( DrawingProgramToolType newToolType ) {
if ( newToolType ! = drawTool - > get_type ( ) ) {
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-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 ( ) ;
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 ( ) {
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 ( ) ) ;
compCache . test_rebuild_dont_include_set_dont_include_nodes ( components . client_list ( ) , eraserTool - > erasedComponents , eraserTool - > erasedBVHNodes , true ) ;
}
2025-08-05 19:54:53 +03:00
else if ( drawTool - > get_type ( ) = = DrawingProgramToolType : : RECTSELECT | | drawTool - > get_type ( ) = = DrawingProgramToolType : : LASSOSELECT )
2025-07-26 21:20:14 +03:00
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 : : initialize_draw_data ( cereal : : PortableBinaryInputArchive & a ) {
uint64_t compCount ;
a ( compCount ) ;
for ( uint64_t i = 0 ; i < compCount ; i + + ) {
DrawComponentType t ;
2025-08-27 11:38:36 +03:00
a ( t ) ;
2025-07-06 20:43:53 +03:00
auto newComp = DrawComponent : : allocate_comp_type ( t ) ;
2025-08-27 11:38:36 +03:00
a ( newComp - > id , newComp - > coords , * newComp ) ;
components . init_emplace_back ( newComp ) ;
2025-07-06 20:43:53 +03:00
}
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 )
2025-08-27 11:38:36 +03:00
idsToErase . emplace ( c - > obj - > id ) ;
2025-07-27 20:04:26 +03:00
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
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-08-27 11:38:36 +03:00
components . client_insert ( objToUndo , false ) ;
2025-07-27 16:30:45 +03:00
objToUndo - > obj - > client_send_place ( * this ) ;
2025-07-06 20:43:53 +03:00
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 )
2025-08-27 11:38:36 +03:00
idsToErase . emplace ( c - > obj - > id ) ;
2025-07-28 11:09:59 +03:00
DrawComponent : : client_send_erase_set ( * this , idsToErase ) ;
components . client_erase_set ( objSetToUndo ) ;
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 ;
2025-08-27 11:38:36 +03:00
components . client_insert_ordered_vector ( sortedObjects , false ) ;
2025-07-28 11:09:59 +03:00
DrawComponent : : client_send_place_many ( * this , sortedObjects ) ;
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 ;
2025-08-27 11:38:36 +03:00
components . client_insert_ordered_vector ( sortedObjects , false ) ;
2025-07-27 20:04:26 +03:00
DrawComponent : : client_send_place_many ( * this , sortedObjects ) ;
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-08-27 11:38:36 +03:00
idsToErase . emplace ( c - > obj - > id ) ;
2025-07-27 20:04:26 +03:00
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
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 ) {
2025-08-27 11:38:36 +03:00
if ( comp - > obj - > id . first = = serverID )
maxClientID = std : : max ( maxClientID , comp - > obj - > id . second ) ;
2025-07-06 20:43:53 +03:00
}
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 + + ) {
2025-08-27 11:38:36 +03:00
a ( cList [ i ] - > obj - > get_type ( ) , cList [ i ] - > obj - > id , cList [ i ] - > obj - > coords , * cList [ i ] - > obj ) ;
2025-07-06 20:43:53 +03:00
}
}
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-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 ( ) ;
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 ;
}