2025-07-06 20:43:53 +03:00
# include "DrawingProgram.hpp"
2025-12-31 11:42:16 +03:00
# include "Helpers/Networking/NetLibrary.hpp"
2025-12-26 10:40:58 +03:00
# include "Tools/DrawingProgramToolBase.hpp"
2025-07-06 20:43:53 +03:00
# include <include/core/SkPaint.h>
# include <include/core/SkVertices.h>
# include "../DrawCamera.hpp"
2025-12-26 10:40:58 +03:00
# include "Tools/EllipseDrawTool.hpp"
# include "Tools/EyeDropperTool.hpp"
# include "Tools/GridModifyTool.hpp"
2025-07-06 20:43:53 +03:00
# include "../InputManager.hpp"
# include "../SharedTypes.hpp"
2025-12-31 11:42:16 +03:00
# include "../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 <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-12-26 10:40:58 +03:00
# include "Tools/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-12-28 17:34:33 +03:00
# include "Layers/DrawingProgramLayer.hpp"
# include "Layers/DrawingProgramLayerListItem.hpp"
2025-12-31 11:42:16 +03:00
# include <Helpers/Parallel.hpp>
2025-12-31 16:30:56 +03:00
# include "../ScaleUpCanvas.hpp"
2025-07-19 19:41:25 +03:00
2025-12-26 10:40:58 +03:00
# include "Tools/EraserTool.hpp"
2025-08-02 20:10:33 +03:00
2025-07-06 20:43:53 +03:00
DrawingProgram : : DrawingProgram ( World & initWorld ) :
world ( initWorld ) ,
2025-12-28 17:34:33 +03:00
drawCache ( * this ) ,
2025-12-29 15:04:32 +03:00
layerMan ( * this ) ,
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
2026-03-02 16:36:34 +03:00
void DrawingProgram : : on_tab_out ( ) {
tempMoveToolSwitch = TemporaryMoveToolSwitch : : NONE ;
selection . deselect_all ( ) ;
controls . leftClickHeld = false ;
controls . middleClickHeld = false ;
}
void DrawingProgram : : input_mouse_button_callback ( const InputManager : : MouseButtonCallbackArgs & button ) {
bool isHoveringOverCanvas = world . main . toolbar . check_if_position_isnt_obstructed ( button . pos ) ;
if ( isHoveringOverCanvas ) {
2026-03-02 23:09:48 +03:00
if ( button . button = = InputManager : : MouseButton : : RIGHT ) {
2026-03-02 16:36:34 +03:00
if ( button . down ) {
if ( world . main . toolbar . rightClickPopupLocation )
world . main . toolbar . rightClickPopupLocation = std : : nullopt ;
else
world . main . toolbar . rightClickPopupLocation = world . main . input . mouse . pos / world . main . toolbar . final_gui_scale ( ) ;
}
2026-02-28 22:47:35 +03:00
}
2026-03-02 16:36:34 +03:00
else {
if ( button . down )
world . main . toolbar . rightClickPopupLocation = std : : nullopt ;
2026-03-02 23:09:48 +03:00
if ( button . button = = InputManager : : MouseButton : : LEFT )
2026-03-02 16:36:34 +03:00
controls . leftClickHeld = button . down ;
2026-03-02 23:09:48 +03:00
else if ( button . button = = InputManager : : MouseButton : : MIDDLE )
2026-03-02 16:36:34 +03:00
controls . middleClickHeld = button . down ;
drawTool - > input_mouse_button_on_canvas_callback ( button ) ;
2026-03-02 23:09:48 +03:00
world . drawData . cam . input_mouse_button_on_canvas_callback ( world , button ) ;
2026-02-28 22:47:35 +03:00
}
2026-03-02 16:36:34 +03:00
}
2026-03-02 23:09:48 +03:00
else if ( ! button . down & & button . button ! = InputManager : : MouseButton : : RIGHT ) {
2026-03-02 16:36:34 +03:00
drawTool - > input_mouse_button_on_canvas_callback ( button ) ;
}
}
void DrawingProgram : : input_mouse_motion_callback ( const InputManager : : MouseMotionCallbackArgs & motion ) {
drawTool - > input_mouse_motion_callback ( motion ) ;
}
void DrawingProgram : : input_key_callback ( const InputManager : : KeyCallbackArgs & key ) {
switch ( key . key ) {
case InputManager : : KEY_DRAW_TOOL_BRUSH : {
if ( key . down & & ! key . repeat )
switch_to_tool ( DrawingProgramToolType : : BRUSH ) ;
break ;
2026-02-28 22:47:35 +03:00
}
2026-03-02 16:36:34 +03:00
case InputManager : : KEY_DRAW_TOOL_ERASER : {
if ( key . down & & ! key . repeat )
switch_to_tool ( DrawingProgramToolType : : ERASER ) ;
break ;
2026-02-28 22:47:35 +03:00
}
2026-03-02 16:36:34 +03:00
case InputManager : : KEY_DRAW_TOOL_RECTSELECT : {
if ( key . down & & ! key . repeat )
switch_to_tool ( DrawingProgramToolType : : RECTSELECT ) ;
break ;
}
case InputManager : : KEY_DRAW_TOOL_RECTANGLE : {
if ( key . down & & ! key . repeat )
switch_to_tool ( DrawingProgramToolType : : RECTANGLE ) ;
break ;
}
case InputManager : : KEY_DRAW_TOOL_ELLIPSE : {
if ( key . down & & ! key . repeat )
switch_to_tool ( DrawingProgramToolType : : ELLIPSE ) ;
break ;
}
case InputManager : : KEY_DRAW_TOOL_TEXTBOX : {
if ( key . down & & ! key . repeat )
switch_to_tool ( DrawingProgramToolType : : TEXTBOX ) ;
break ;
}
case InputManager : : KEY_DRAW_TOOL_EYEDROPPER : {
if ( key . down & & ! key . repeat )
switch_to_tool ( DrawingProgramToolType : : EYEDROPPER ) ;
break ;
}
case InputManager : : KEY_DRAW_TOOL_SCREENSHOT : {
if ( key . down & & ! key . repeat )
switch_to_tool ( DrawingProgramToolType : : SCREENSHOT ) ;
break ;
}
case InputManager : : KEY_DRAW_TOOL_ZOOM : {
if ( key . down & & ! key . repeat )
switch_to_tool ( DrawingProgramToolType : : ZOOM ) ;
break ;
}
case InputManager : : KEY_DRAW_TOOL_LASSOSELECT : {
if ( key . down & & ! key . repeat )
switch_to_tool ( DrawingProgramToolType : : LASSOSELECT ) ;
break ;
}
case InputManager : : KEY_DRAW_TOOL_PAN : {
if ( key . down & & ! key . repeat )
switch_to_tool ( DrawingProgramToolType : : PAN ) ;
break ;
}
case InputManager : : KEY_DRAW_TOOL_LINE : {
if ( key . down & & ! key . repeat )
switch_to_tool ( DrawingProgramToolType : : LINE ) ;
break ;
}
case InputManager : : KEY_HOLD_TO_PAN : {
if ( key . down & & ! key . repeat & & tempMoveToolSwitch = = TemporaryMoveToolSwitch : : NONE ) {
toolTypeAfterTempMove = drawTool - > get_type ( ) ;
switch_to_tool ( DrawingProgramToolType : : PAN ) ;
tempMoveToolSwitch = TemporaryMoveToolSwitch : : PAN ;
2026-03-01 17:28:43 +03:00
}
2026-03-02 16:36:34 +03:00
else if ( ! key . down & & tempMoveToolSwitch = = TemporaryMoveToolSwitch : : PAN ) {
switch_to_tool ( toolTypeAfterTempMove ) ;
tempMoveToolSwitch = TemporaryMoveToolSwitch : : NONE ;
2026-03-01 17:28:43 +03:00
}
2026-03-02 16:36:34 +03:00
break ;
2026-03-01 17:28:43 +03:00
}
2026-03-02 16:36:34 +03:00
case InputManager : : KEY_HOLD_TO_ZOOM : {
if ( key . down & & ! key . repeat & & tempMoveToolSwitch = = TemporaryMoveToolSwitch : : NONE ) {
toolTypeAfterTempMove = drawTool - > get_type ( ) ;
switch_to_tool ( DrawingProgramToolType : : ZOOM ) ;
tempMoveToolSwitch = TemporaryMoveToolSwitch : : ZOOM ;
}
else if ( ! key . down & & tempMoveToolSwitch = = TemporaryMoveToolSwitch : : ZOOM ) {
switch_to_tool ( toolTypeAfterTempMove ) ;
tempMoveToolSwitch = TemporaryMoveToolSwitch : : NONE ;
}
break ;
}
}
drawTool - > input_key_callback ( key ) ;
2026-02-28 22:47:35 +03:00
}
2026-01-01 22:46:02 +03:00
void DrawingProgram : : server_init_no_file ( ) {
layerMan . server_init_no_file ( ) ;
2025-07-06 20:43:53 +03:00
}
2025-12-14 18:57:40 +03:00
void DrawingProgram : : scale_up ( const WorldScalar & scaleUpAmount ) {
2025-12-31 16:30:56 +03:00
selection . deselect_all ( ) ;
layerMan . scale_up ( scaleUpAmount ) ;
2026-01-29 11:48:08 +03:00
if ( controls . lockedCameraScale . has_value ( ) )
controls . lockedCameraScale . value ( ) * = scaleUpAmount ;
2025-12-31 16:30:56 +03:00
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-15 16:49:51 +03:00
void DrawingProgram : : write_components_server ( cereal : : PortableBinaryOutputArchive & a ) {
2025-12-25 17:18:28 +03:00
layerMan . write_components_server ( a ) ;
2025-12-15 16:49:51 +03:00
}
void DrawingProgram : : read_components_client ( cereal : : PortableBinaryInputArchive & a ) {
2025-12-25 17:18:28 +03:00
layerMan . read_components_client ( a ) ;
2025-12-28 17:34:33 +03:00
drawCache . build ( { } ) ;
2025-12-15 16:49:51 +03:00
}
2025-12-31 11:42:16 +03:00
void DrawingProgram : : init_server_callbacks ( ) {
world . netServer - > add_recv_callback ( SERVER_TRANSFORM_MANY_COMPONENTS , [ & ] ( std : : shared_ptr < NetServer : : ClientData > client , cereal : : PortableBinaryInputArchive & message ) {
std : : vector < std : : pair < NetworkingObjects : : NetObjID , CoordSpaceHelper > > transforms ;
message ( transforms ) ;
2025-12-31 16:30:56 +03:00
auto clientData = world . netObjMan . get_obj_temporary_ref_from_id < ClientData > ( NetworkingObjects : : NetObjID ( client - > customID ) ) ;
if ( clientData - > get_grid_size ( ) < world . ownClientData - > get_grid_size ( ) ) {
WorldScalar scaleUpAmount = get_canvas_scale_up_amount ( world . ownClientData - > get_grid_size ( ) , clientData - > get_grid_size ( ) ) ;
for ( auto & [ netID , coord ] : transforms )
2025-12-31 16:43:08 +03:00
coord . scale_about ( WorldVec { 0 , 0 } , scaleUpAmount , true ) ;
2025-12-31 16:30:56 +03:00
}
2025-12-31 11:42:16 +03:00
process_transform_message ( transforms ) ;
world . netServer - > send_items_to_all_clients ( RELIABLE_COMMAND_CHANNEL , CLIENT_TRANSFORM_MANY_COMPONENTS , transforms ) ;
} ) ;
}
void DrawingProgram : : init_client_callbacks ( ) {
2026-01-01 22:46:02 +03:00
world . netClient - > add_recv_callback ( CLIENT_TRANSFORM_MANY_COMPONENTS , [ & ] ( cereal : : PortableBinaryInputArchive & message ) {
2025-12-31 11:42:16 +03:00
std : : vector < std : : pair < NetworkingObjects : : NetObjID , CoordSpaceHelper > > transforms ;
message ( transforms ) ;
process_transform_message ( transforms ) ;
} ) ;
}
void DrawingProgram : : process_transform_message ( const std : : vector < std : : pair < NetworkingObjects : : NetObjID , CoordSpaceHelper > > & transforms ) {
2026-01-01 08:34:49 +03:00
auto selectedSet = selection . get_selection_as_set ( ) ;
2025-12-31 11:42:16 +03:00
for ( auto & [ id , coords ] : transforms ) {
auto objPtr = world . netObjMan . get_obj_temporary_ref_from_id < CanvasComponentContainer > ( id ) ;
2026-01-01 08:34:49 +03:00
if ( ! objPtr | | selectedSet . contains ( & ( * objPtr - > objInfo ) ) ) // Whatever transformation we're doing right now will overwrite this transformation, so we can ignore this message
continue ;
2025-12-31 11:42:16 +03:00
if ( objPtr - > coords = = coords )
2026-01-01 08:34:49 +03:00
continue ;
2025-12-31 11:42:16 +03:00
objPtr - > coords = coords ;
objPtr - > commit_transform ( * this ) ;
}
}
void DrawingProgram : : send_transforms_for ( const std : : vector < CanvasComponentContainer : : ObjInfo * > & objsToSendTransformsFor ) {
if ( world . netObjMan . is_connected ( ) ) {
std : : vector < std : : pair < NetworkingObjects : : NetObjID , CoordSpaceHelper > > transforms ;
for ( auto & obj : objsToSendTransformsFor )
transforms . emplace_back ( obj - > obj . get_net_id ( ) , obj - > obj - > coords ) ;
if ( world . netObjMan . is_server ( ) )
world . netServer - > send_items_to_all_clients ( RELIABLE_COMMAND_CHANNEL , CLIENT_TRANSFORM_MANY_COMPONENTS , transforms ) ;
else
2026-01-01 22:46:02 +03:00
world . netClient - > send_items_to_server ( RELIABLE_COMMAND_CHANNEL , SERVER_TRANSFORM_MANY_COMPONENTS , transforms ) ;
2025-12-31 11:42:16 +03:00
}
}
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 " ) ;
2026-02-27 16:16:51 +03:00
Clay_ElementId localID = CLAY_ID_LOCAL ( " Drawing program toolbar GUI " ) ;
CLAY ( localID , {
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
} ) {
2026-02-27 16:16:51 +03:00
t . gui . obstructing_window ( localID ) ;
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 ) ; }
2026-01-08 11:13:49 +03:00
if ( t . gui . svg_icon_button_transparent ( " Line Toolbar Button " , " data/icons/line.svg " , drawTool - > get_type ( ) = = DrawingProgramToolType : : LINE ) ) { switch_to_tool ( DrawingProgramToolType : : LINE ) ; }
2025-11-01 16:07:19 +03:00
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 ) ;
2026-01-26 19:08:36 +03:00
auto & globalConf = world . main . toolConfig . globalConf ;
if ( t . gui . big_color_button ( " Foreground Color " , & globalConf . foregroundColor , & globalConf . foregroundColor = = t . colorLeft ) )
t . color_selector_left ( & globalConf . foregroundColor = = t . colorLeft ? nullptr : & globalConf . foregroundColor ) ;
if ( t . gui . big_color_button ( " Background Color " , & globalConf . backgroundColor , & globalConf . backgroundColor = = t . colorLeft ) )
t . color_selector_left ( & globalConf . backgroundColor = = t . colorLeft ? nullptr : & globalConf . 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 " ) ;
2025-12-29 15:04:32 +03:00
if ( t . gui . text_button_left_transparent ( " Paste " , " Paste " ) ) {
selection . deselect_all ( ) ;
selection . paste_clipboard ( popupPos * t . final_gui_scale ( ) ) ;
shouldClose = true ;
}
2026-01-05 19:15:42 +03:00
if ( t . gui . text_button_left_transparent ( " Paste Image " , " Paste Image " ) ) {
2026-01-06 19:06:44 +03:00
selection . deselect_all ( ) ;
selection . paste_image ( popupPos * t . final_gui_scale ( ) ) ;
2026-01-05 19:15:42 +03:00
shouldClose = true ;
}
2025-12-29 15:04:32 +03:00
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 ;
}
2026-01-20 11:59:03 +03:00
if ( t . gui . text_button_left_transparent ( " Bring to front of layer " , " Bring to front of layer " ) ) {
selection . push_selection_to_front ( ) ;
shouldClose = true ;
}
if ( t . gui . text_button_left_transparent ( " Send to back of layer " , " Send to back of layer " ) ) {
selection . push_selection_to_back ( ) ;
shouldClose = true ;
}
2025-12-29 15:04:32 +03:00
}
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 ;
2026-02-27 16:16:51 +03:00
Clay_ElementId localID = CLAY_ID_LOCAL ( " Drawing program tool options gui " ) ;
CLAY ( localID , {
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
} ) {
2026-02-27 16:16:51 +03:00
t . gui . obstructing_window ( localID ) ;
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 ( ) {
2026-01-07 10:34:51 +03:00
tool_temporary_switch_update ( ) ;
2025-09-28 18:25:46 +03:00
2026-03-01 17:28:43 +03:00
controls . cursorHoveringOverCanvas = world . main . toolbar . check_if_position_isnt_obstructed ( world . main . input . mouse . pos ) ;
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-08-02 20:10:33 +03:00
2025-12-29 15:04:32 +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 ( ) ;
2026-01-27 12:40:39 +03:00
if ( drawCache . check_rebuild_needed_from_framerate ( ) | | drawCache . should_rebuild ( ) )
2025-12-28 17:34:33 +03:00
rebuild_cache ( ) ;
2025-07-26 21:20:14 +03:00
}
2026-01-07 10:34:51 +03:00
void DrawingProgram : : tool_temporary_switch_update ( ) {
if ( world . main . input . pen . isEraser & & ! temporaryEraser & & tempMoveToolSwitch = = TemporaryMoveToolSwitch : : NONE ) {
if ( drawTool - > get_type ( ) = = DrawingProgramToolType : : BRUSH )
switch_to_tool ( DrawingProgramToolType : : ERASER ) ;
temporaryEraser = true ;
}
else if ( ! world . main . input . pen . isEraser & & temporaryEraser & & tempMoveToolSwitch = = TemporaryMoveToolSwitch : : NONE ) {
if ( drawTool - > get_type ( ) = = DrawingProgramToolType : : ERASER )
switch_to_tool ( DrawingProgramToolType : : BRUSH ) ;
temporaryEraser = false ;
}
}
2025-12-30 19:46:08 +03:00
void DrawingProgram : : invalidate_cache_at_component ( CanvasComponentContainer : : ObjInfo * objToCheck ) {
2025-12-29 15:04:32 +03:00
if ( ! selection . is_selected ( objToCheck ) )
drawCache . invalidate_cache_at_aabb ( objToCheck - > obj - > get_world_bounds ( ) ) ;
}
2025-12-30 19:46:08 +03:00
void DrawingProgram : : preupdate_component ( CanvasComponentContainer : : ObjInfo * objToCheck ) {
2025-12-29 15:04:32 +03:00
if ( ! selection . is_selected ( objToCheck ) )
drawCache . preupdate_component ( objToCheck ) ;
}
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-12-28 17:34:33 +03:00
void DrawingProgram : : 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-28 17:34:33 +03:00
drawCache . build ( eraserTool - > erasedComponents ) ;
}
2025-12-31 10:06:26 +03:00
else if ( is_selection_allowing_tool ( drawTool - > get_type ( ) ) )
drawCache . build ( selection . get_selection_as_set ( ) ) ;
2025-07-26 21:20:14 +03:00
else
2025-12-28 17:34:33 +03:00
drawCache . build ( { } ) ;
2025-11-16 16:39:12 +03:00
}
2025-12-16 12:54:12 +03:00
void DrawingProgram : : drag_drop_update ( ) {
2025-12-28 17:34:33 +03:00
if ( controls . cursorHoveringOverCanvas & & layerMan . is_a_layer_being_edited ( ) ) {
2025-12-16 12:54:12 +03:00
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 } ;
2025-12-28 17:34:33 +03:00
auto newObjInfo = layerMan . add_component_to_layer_being_edited ( newContainer ) ;
2026-01-06 12:55:44 +03:00
layerMan . add_undo_place_component ( newObjInfo ) ;
2025-12-16 12:54:12 +03:00
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 " ) ;
2025-12-28 17:34:33 +03:00
auto & parentLayerComponents = downFile . comp - > obj - > parentLayer - > get_layer ( ) . components ;
2025-12-30 19:46:08 +03:00
parentLayerComponents - > erase ( parentLayerComponents , downFile . comp - > obj - > objInfo ) ;
2025-12-16 12:54:12 +03:00
}
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 ;
}
2025-12-28 17:34:33 +03:00
case FileDownloader : : DownloadData : : Status : : FAILURE : {
2025-12-16 12:54:12 +03:00
Logger : : get ( ) . log ( " WORLDFATAL " , " Failed to download data from URL " ) ;
2025-12-28 17:34:33 +03:00
auto & parentLayerComponents = downFile . comp - > obj - > parentLayer - > get_layer ( ) . components ;
2025-12-30 19:46:08 +03:00
parentLayerComponents - > erase ( parentLayerComponents , downFile . comp - > obj - > objInfo ) ;
2025-12-16 12:54:12 +03:00
return true ;
2025-12-28 17:34:33 +03:00
}
2025-12-16 12:54:12 +03:00
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 ) {
2026-01-05 19:15:42 +03:00
if ( layerMan . is_a_layer_being_edited ( ) ) {
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 ;
2026-01-06 12:55:44 +03:00
auto newObjInfo = layerMan . add_component_to_layer_being_edited ( newContainer ) ;
layerMan . add_undo_place_component ( newObjInfo ) ;
2026-01-05 19:15:42 +03:00
}
}
}
2026-01-06 19:06:44 +03:00
CanvasComponentContainer : : ObjInfo * DrawingProgram : : add_file_to_canvas_by_data ( const std : : string & fileName , std : : string_view fileBuffer , Vector2f dropPos ) {
2026-01-05 19:15:42 +03:00
if ( layerMan . is_a_layer_being_edited ( ) ) {
ResourceData newResource ;
newResource . data = std : : make_shared < std : : string > ( fileBuffer ) ;
newResource . name = fileName ;
NetworkingObjects : : NetObjID imageID = world . rMan . add_resource ( newResource ) . get_net_id ( ) ;
2025-12-16 12:54:12 +03:00
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 ;
2026-01-06 19:06:44 +03:00
CanvasComponentContainer : : ObjInfo * newObjInfo = layerMan . add_component_to_layer_being_edited ( newContainer ) ;
2026-01-06 12:55:44 +03:00
layerMan . add_undo_place_component ( newObjInfo ) ;
2026-01-06 19:06:44 +03:00
return newObjInfo ;
2025-12-16 12:54:12 +03:00
}
2026-01-06 19:06:44 +03:00
return nullptr ;
2025-12-16 12:54:12 +03:00
}
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 ) {
2026-01-01 22:46:02 +03:00
layerMan . load_file ( a , version ) ;
2025-12-14 18:57:40 +03:00
}
2025-11-16 16:39:12 +03:00
void DrawingProgram : : save_file ( cereal : : PortableBinaryOutputArchive & a ) const {
2026-01-01 22:46:02 +03:00
layerMan . save_file ( a ) ;
2025-07-06 20:43:53 +03:00
}
2026-01-01 23:28:26 +03:00
void DrawingProgram : : get_used_resources ( std : : unordered_set < NetworkingObjects : : NetObjID > & resourceSet ) {
layerMan . get_used_resources ( resourceSet ) ;
}
2025-07-06 20:43:53 +03:00
void DrawingProgram : : draw ( SkCanvas * canvas , const DrawData & drawData ) {
2026-02-25 13:26:35 +03:00
if ( drawData . takingScreenshot )
2025-12-28 17:34:33 +03:00
layerMan . draw ( canvas , drawData ) ;
else {
canvas - > saveLayer ( nullptr , nullptr ) ;
canvas - > clear ( SkColor4f { 0.0f , 0.0f , 0.0f , 0.0f } ) ;
2026-01-21 13:11:01 +03:00
drawCache . update_and_draw_cached_canvas ( canvas , drawData ) ;
2025-12-29 15:04:32 +03:00
canvas - > saveLayer ( nullptr , nullptr ) ;
selection . draw_components ( canvas , drawData ) ;
canvas - > restore ( ) ;
2025-12-28 17:34:33 +03:00
canvas - > restore ( ) ;
2026-02-25 13:26:35 +03:00
for ( auto & droppedDownFile : droppedDownloadingFiles )
static_cast < ImageCanvasComponent & > ( droppedDownFile . comp - > obj - > get_comp ( ) ) . draw_download_progress_bar ( canvas , drawData , droppedDownFile . downData - > progress ) ;
2025-12-28 17:34:33 +03:00
2026-02-25 13:26:35 +03:00
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-12-28 17:34:33 +03:00
}
}
2025-12-29 15:04:32 +03:00
selection . draw_gui ( canvas , drawData ) ;
2026-02-25 13:26:35 +03:00
drawTool - > draw ( canvas , drawData ) ;
2025-12-28 17:34:33 +03:00
}
2025-07-06 20:43:53 +03:00
}
Vector4f * DrawingProgram : : get_foreground_color_ptr ( ) {
2026-01-26 19:08:36 +03:00
return & world . main . toolConfig . globalConf . foregroundColor ;
2025-07-06 20:43:53 +03:00
}