2025-12-29 15:04:32 +03:00
# include "DrawingProgramSelection.hpp"
# include "DrawingProgram.hpp"
# include "../World.hpp"
# include "../MainProgram.hpp"
# include "../InputManager.hpp"
# include <Helpers/Parallel.hpp>
2025-12-29 19:57:36 +03:00
# include <Helpers/Logger.hpp>
2026-01-23 09:37:57 +03:00
# include <chrono>
2025-12-29 15:04:32 +03:00
# ifdef USE_SKIA_BACKEND_GRAPHITE
# include <include/gpu/graphite/Surface.h>
# elif USE_SKIA_BACKEND_GANESH
# include <include/gpu/ganesh/GrDirectContext.h>
# include <include/gpu/ganesh/SkSurfaceGanesh.h>
# endif
# include <include/effects/SkImageFilters.h>
# include <include/core/SkPathBuilder.h>
# define ROTATION_POINT_RADIUS_MULTIPLIER 0.7f
# define ROTATION_POINTS_DISTANCE 20.0f
2026-01-06 19:06:44 +03:00
# ifdef __EMSCRIPTEN__
# include <EmscriptenHelpers/emscripten_browser_clipboard.h>
# endif
2026-03-30 17:05:24 +03:00
# include "../GUIStuff/ElementHelpers/TextLabelHelpers.hpp"
# include "../GUIStuff/ElementHelpers/RadioButtonHelpers.hpp"
# include "../GUIStuff/ElementHelpers/LayoutHelpers.hpp"
2025-12-29 15:04:32 +03:00
DrawingProgramSelection : : DrawingProgramSelection ( DrawingProgram & initDrawP ) :
drawP ( initDrawP )
{ }
2026-01-27 16:55:09 +03:00
void DrawingProgramSelection : : selection_gui ( ) {
2026-03-30 17:05:24 +03:00
using namespace GUIStuff ;
using namespace ElementHelpers ;
2026-01-27 16:55:09 +03:00
auto & t = drawP . world . main . toolbar ;
2026-03-30 18:51:14 +03:00
t . gui . new_id ( " general selection gui " , [ & ] {
text_label ( t . gui , " Select from: " ) ;
radio_button_selector ( t . gui , " layer selector " , & drawP . controls . layerSelector , {
{ " Layer being edited " , DrawingProgramLayerManager : : LayerSelector : : LAYER_BEING_EDITED } ,
{ " All visible layers " , DrawingProgramLayerManager : : LayerSelector : : ALL_VISIBLE_LAYERS }
2026-01-27 16:55:09 +03:00
} ) ;
2026-03-30 18:51:14 +03:00
if ( is_something_selected ( ) ) {
left_to_right_line_layout ( t . gui , [ & ] ( ) {
//if(t.gui.color_button_big("Stroke Color Button", &strokeColorChangeData.newColor, &strokeColorChangeData.newColor == t.colorRight)) {
// if(strokeColorChangeData.newColor.w() == 0.0f)
// strokeColorChangeData.newColor.w() = 1.0f;
// t.color_selector_right(&strokeColorChangeData.newColor == t.colorRight ? nullptr : &strokeColorChangeData.newColor);
//}
text_label ( t . gui , " Stroke Color " ) ;
} ) ;
update_selection_stroke_color ( ) ;
}
} ) ;
2026-01-27 16:55:09 +03:00
}
void DrawingProgramSelection : : update_selection_stroke_color ( ) {
if ( strokeColorChangeData . oldColor ! = strokeColorChangeData . newColor ) {
for ( auto & c : selectedSet ) {
if ( c - > obj - > get_comp ( ) . get_stroke_color ( ) ! = std : : nullopt ) {
strokeColorChangeData . oldColorData . emplace ( c - > obj . get_net_id ( ) , c - > obj - > get_comp ( ) . get_stroke_color ( ) . value ( ) ) ;
c - > obj - > get_comp ( ) . change_stroke_color ( strokeColorChangeData . newColor ) ;
c - > obj - > commit_update_dont_invalidate_cache ( drawP ) ; // Object isn't in the cache since it's selected
c - > obj - > send_comp_update ( drawP , false ) ;
}
}
strokeColorChangeData . oldColor = strokeColorChangeData . newColor ;
}
}
void DrawingProgramSelection : : check_add_stroke_color_change_undo ( ) {
if ( ! strokeColorChangeData . oldColorData . empty ( ) ) {
class EditCanvasComponentsStrokeColorWorldUndoAction : public WorldUndoAction {
public :
EditCanvasComponentsStrokeColorWorldUndoAction ( std : : vector < WorldUndoManager : : UndoObjectID > initUndoIDs , std : : vector < Vector4f > initOldColors ) :
undoIDs ( std : : move ( initUndoIDs ) ) ,
colors ( std : : move ( initOldColors ) )
{ }
std : : string get_name ( ) const override {
return " Edit Stroke Colors " ;
}
bool undo ( WorldUndoManager & undoMan ) override {
return undo_redo ( undoMan ) ;
}
bool redo ( WorldUndoManager & undoMan ) override {
return undo_redo ( undoMan ) ;
}
bool undo_redo ( WorldUndoManager & undoMan ) {
std : : vector < NetworkingObjects : : NetObjID > netIDs ;
if ( ! undoMan . fill_netid_list_from_undoid_list ( netIDs , undoIDs ) )
return false ;
undoMan . world . netObjMan . send_multi_update_messsage ( [ & ] ( ) {
for ( size_t i = 0 ; i < netIDs . size ( ) ; i + + ) {
auto objPtr = undoMan . world . netObjMan . get_obj_temporary_ref_from_id < CanvasComponentContainer > ( netIDs [ i ] ) ;
if ( objPtr ) {
Vector4f oldColor = objPtr - > get_comp ( ) . get_stroke_color ( ) . value ( ) ;
objPtr - > get_comp ( ) . change_stroke_color ( colors [ i ] ) ;
colors [ i ] = oldColor ;
objPtr - > commit_update ( undoMan . world . drawProg ) ;
objPtr - > send_comp_update ( undoMan . world . drawProg , true ) ;
}
}
} , NetworkingObjects : : NetObjManager : : SendUpdateType : : SEND_TO_ALL , nullptr ) ;
return true ;
}
~ EditCanvasComponentsStrokeColorWorldUndoAction ( ) { }
std : : vector < WorldUndoManager : : UndoObjectID > undoIDs ;
std : : vector < Vector4f > colors ;
} ;
std : : vector < WorldUndoManager : : UndoObjectID > undoIDs ;
std : : vector < Vector4f > oldColors ;
for ( auto & [ netID , strokeColor ] : strokeColorChangeData . oldColorData ) {
undoIDs . emplace_back ( drawP . world . undo . get_undoid_from_netid ( netID ) ) ;
oldColors . emplace_back ( strokeColor ) ;
}
drawP . world . undo . push ( std : : make_unique < EditCanvasComponentsStrokeColorWorldUndoAction > ( std : : move ( undoIDs ) , std : : move ( oldColors ) ) ) ;
strokeColorChangeData . oldColorData . clear ( ) ;
auto & t = drawP . world . main . toolbar ;
if ( & strokeColorChangeData . newColor = = t . colorRight )
t . colorRight = nullptr ; // Just making sure that the color selector doesn't leak
}
}
2025-12-29 15:04:32 +03:00
void DrawingProgramSelection : : add_from_cam_coord_collider_to_selection ( const SCollision : : ColliderCollection < float > & cC , DrawingProgramLayerManager : : LayerSelector layerSelector , bool frontObjectOnly ) {
2026-01-27 16:55:09 +03:00
check_add_stroke_color_change_undo ( ) ;
2025-12-31 10:06:26 +03:00
std : : vector < CanvasComponentContainer : : ObjInfo * > selectedComponents ;
2025-12-29 15:04:32 +03:00
if ( frontObjectOnly ) {
2025-12-30 19:46:08 +03:00
CanvasComponentContainer : : ObjInfo * a = drawP . drawCache . get_front_object_colliding_with_in_editing_layer ( cC ) ;
2025-12-29 15:04:32 +03:00
if ( a ) {
2025-12-31 10:06:26 +03:00
selectedComponents . emplace_back ( a ) ;
2025-12-29 15:04:32 +03:00
drawP . drawCache . erase_component ( a ) ;
}
}
else {
auto cCWorld = drawP . world . drawData . cam . c . collider_to_world < SCollision : : ColliderCollection < WorldScalar > , SCollision : : ColliderCollection < float > > ( cC ) ;
drawP . drawCache . traverse_bvh_run_function ( cCWorld . bounds , erase_select_objects_in_bvh_func ( selectedComponents , cC , cCWorld , layerSelector ) ) ;
}
add_to_selection ( selectedComponents ) ;
}
void DrawingProgramSelection : : remove_from_cam_coord_collider_to_selection ( const SCollision : : ColliderCollection < float > & cC , DrawingProgramLayerManager : : LayerSelector layerSelector , bool frontObjectOnly ) {
2026-01-27 16:55:09 +03:00
check_add_stroke_color_change_undo ( ) ;
2025-12-29 15:04:32 +03:00
if ( frontObjectOnly ) {
2025-12-30 19:46:08 +03:00
CanvasComponentContainer : : ObjInfo * p = get_front_object_colliding_with_in_editing_layer ( cC ) ;
2025-12-29 15:04:32 +03:00
if ( p ) {
drawP . drawCache . add_component ( p ) ;
2025-12-31 10:06:26 +03:00
std : : erase ( selectedSet , p ) ;
2025-12-29 15:04:32 +03:00
}
}
else {
auto cCWorld = drawP . world . drawData . cam . c . collider_to_world < SCollision : : ColliderCollection < WorldScalar > , SCollision : : ColliderCollection < float > > ( cC ) ;
std : : erase_if ( selectedSet , [ & ] ( auto & c ) {
if ( drawP . layerMan . component_passes_layer_selector ( c , layerSelector ) & & c - > obj - > collides_with ( drawP . world . drawData . cam . c , cCWorld , cC ) ) {
drawP . drawCache . add_component ( c ) ;
return true ;
}
return false ;
} ) ;
}
calculate_aabb ( ) ;
}
2025-12-31 10:06:26 +03:00
std : : function < bool ( const std : : shared_ptr < DrawingProgramCacheBVHNode > & ) > DrawingProgramSelection : : erase_select_objects_in_bvh_func ( std : : vector < CanvasComponentContainer : : ObjInfo * > & selectedComponents , const SCollision : : ColliderCollection < float > & cC , const SCollision : : ColliderCollection < WorldScalar > & cCWorld , DrawingProgramLayerManager : : LayerSelector layerSelector ) {
2025-12-29 15:04:32 +03:00
auto toRet = [ & ] ( const auto & bvhNode ) {
if ( bvhNode & & ( bvhNode - > coords . inverseScale < < 9 ) < drawP . world . drawData . cam . c . inverseScale & &
SCollision : : collide ( cC , drawP . world . drawData . cam . c . to_space ( bvhNode - > bounds . min ) ) & &
SCollision : : collide ( cC , drawP . world . drawData . cam . c . to_space ( bvhNode - > bounds . max ) ) & &
SCollision : : collide ( cC , drawP . world . drawData . cam . c . to_space ( bvhNode - > bounds . top_right ( ) ) ) & &
SCollision : : collide ( cC , drawP . world . drawData . cam . c . to_space ( bvhNode - > bounds . bottom_left ( ) ) ) ) {
drawP . drawCache . invalidate_cache_at_aabb ( bvhNode - > bounds ) ;
drawP . drawCache . traverse_bvh_run_function_starting_at_node_no_collision_check ( bvhNode , [ & ] ( const auto & bvhNodeChild ) {
2025-12-30 19:46:08 +03:00
drawP . drawCache . node_loop_erase_if_components ( bvhNodeChild , [ & ] ( auto c ) {
2025-12-29 15:04:32 +03:00
if ( drawP . layerMan . component_passes_layer_selector ( c , drawP . controls . layerSelector ) ) {
2025-12-31 10:06:26 +03:00
selectedComponents . emplace_back ( c ) ;
2025-12-29 15:04:32 +03:00
return true ;
}
return false ;
} ) ;
return true ;
} ) ;
return false ;
}
2025-12-30 19:46:08 +03:00
drawP . drawCache . node_loop_erase_if_components ( bvhNode , [ & ] ( auto c ) {
2025-12-29 15:04:32 +03:00
if ( drawP . layerMan . component_passes_layer_selector ( c , drawP . controls . layerSelector ) & & c - > obj - > collides_with ( drawP . world . drawData . cam . c , cCWorld , cC ) ) {
2025-12-31 10:06:26 +03:00
selectedComponents . emplace_back ( c ) ;
2025-12-29 15:04:32 +03:00
drawP . drawCache . invalidate_cache_at_aabb ( c - > obj - > get_world_bounds ( ) ) ;
return true ;
}
return false ;
} ) ;
return true ;
} ;
return toRet ;
}
2025-12-31 10:06:26 +03:00
void DrawingProgramSelection : : add_to_selection ( const std : : vector < CanvasComponentContainer : : ObjInfo * > & newSelection ) {
selectedSet . insert ( selectedSet . end ( ) , newSelection . begin ( ) , newSelection . end ( ) ) ;
sort_selection ( ) ;
2025-12-29 15:04:32 +03:00
calculate_aabb ( ) ;
}
2025-12-31 10:06:26 +03:00
void DrawingProgramSelection : : set_to_selection ( const std : : vector < CanvasComponentContainer : : ObjInfo * > & newSelection ) {
2025-12-29 15:04:32 +03:00
selectedSet = newSelection ;
2025-12-31 10:06:26 +03:00
sort_selection ( ) ;
2025-12-29 15:04:32 +03:00
calculate_aabb ( ) ;
}
2025-12-31 10:06:26 +03:00
void DrawingProgramSelection : : sort_selection ( ) {
std : : vector < DrawingProgramLayerListItem * > flattenedLayerList = drawP . layerMan . get_flattened_layer_list ( ) ;
std : : unordered_map < DrawingProgramLayerListItem * , size_t > flattenedLayerListOrder ;
for ( size_t i = 0 ; i < flattenedLayerList . size ( ) ; i + + )
2025-12-31 10:10:58 +03:00
flattenedLayerListOrder [ flattenedLayerList [ i ] ] = flattenedLayerList . size ( ) - 1 - i ;
2025-12-31 10:06:26 +03:00
std : : sort ( selectedSet . begin ( ) , selectedSet . end ( ) , [ & ] ( auto & a , auto & b ) {
return ( flattenedLayerListOrder [ a - > obj - > parentLayer ] < flattenedLayerListOrder [ b - > obj - > parentLayer ] ) | | ( a - > obj - > parentLayer = = b - > obj - > parentLayer & & a - > pos < b - > pos ) ;
} ) ;
}
2025-12-29 15:04:32 +03:00
void DrawingProgramSelection : : calculate_aabb ( ) {
if ( is_something_selected ( ) ) {
initialSelectionAABB = ( * selectedSet . begin ( ) ) - > obj - > get_world_bounds ( ) ;
for ( auto & c : selectedSet )
initialSelectionAABB . include_aabb_in_bounds ( c - > obj - > get_world_bounds ( ) ) ;
rotateData . centerPos = initialSelectionAABB . center ( ) ;
}
}
bool DrawingProgramSelection : : is_something_selected ( ) {
return ! selectedSet . empty ( ) ;
}
2025-12-30 19:46:08 +03:00
bool DrawingProgramSelection : : is_selected ( CanvasComponentContainer : : ObjInfo * objToCheck ) {
2025-12-31 10:06:26 +03:00
return std : : find ( selectedSet . begin ( ) , selectedSet . end ( ) , objToCheck ) ! = selectedSet . end ( ) ;
2025-12-29 15:04:32 +03:00
}
void DrawingProgramSelection : : reset_all ( ) {
2026-01-27 16:55:09 +03:00
check_add_stroke_color_change_undo ( ) ;
2025-12-29 15:04:32 +03:00
selectedSet . clear ( ) ;
2026-01-01 08:34:49 +03:00
reset_transform_data ( ) ;
2026-01-27 16:55:09 +03:00
strokeColorChangeData = StrokeColorChangeData ( ) ;
2026-01-01 08:34:49 +03:00
}
void DrawingProgramSelection : : reset_transform_data ( ) {
2026-01-27 16:55:09 +03:00
check_add_stroke_color_change_undo ( ) ;
2025-12-29 15:04:32 +03:00
selectionTransformCoords = CoordSpaceHelperTransform ( ) ;
transformOpHappening = TransformOperation : : NONE ;
translateData = TranslationData ( ) ;
scaleData = ScaleData ( ) ;
rotateData = RotationData ( ) ;
}
bool DrawingProgramSelection : : mouse_collided_with_selection_aabb ( ) {
return SCollision : : collide ( camSpaceSelection , drawP . world . main . input . mouse . pos ) ;
}
bool DrawingProgramSelection : : mouse_collided_with_scale_point ( ) {
return SCollision : : collide ( SCollision : : Circle ( scaleData . handlePoint , drawP . drag_point_radius ( ) ) , drawP . world . main . input . mouse . pos ) ;
}
bool DrawingProgramSelection : : mouse_collided_with_rotate_center_handle_point ( ) {
return SCollision : : collide ( SCollision : : Circle ( rotateData . centerHandlePoint , drawP . drag_point_radius ( ) ) , drawP . world . main . input . mouse . pos ) ;
}
bool DrawingProgramSelection : : mouse_collided_with_rotate_handle_point ( ) {
return SCollision : : collide ( SCollision : : Circle ( rotateData . handlePoint , drawP . drag_point_radius ( ) * ROTATION_POINT_RADIUS_MULTIPLIER ) , drawP . world . main . input . mouse . pos ) ;
}
void DrawingProgramSelection : : commit_transform_selection ( ) {
2026-01-27 16:55:09 +03:00
check_add_stroke_color_change_undo ( ) ;
2026-01-05 15:37:21 +03:00
if ( ! is_empty_transform ( ) ) {
2026-01-04 17:44:12 +03:00
std : : vector < WorldUndoManager : : UndoObjectID > undoIDList ;
std : : vector < std : : pair < CoordSpaceHelper , CoordSpaceHelper > > transformDataList ;
for ( auto & comp : selectedSet ) {
undoIDList . emplace_back ( drawP . world . undo . get_undoid_from_netid ( comp - > obj . get_net_id ( ) ) ) ;
auto & transformData = transformDataList . emplace_back ( ) ;
transformData . first = comp - > obj - > coords ;
comp - > obj - > coords = selectionTransformCoords . other_coord_space_from_this_space ( comp - > obj - > coords ) ;
transformData . second = comp - > obj - > coords ;
comp - > obj - > commit_transform_dont_invalidate_cache ( ) ;
}
class TransformCanvasComponentsWorldUndoAction : public WorldUndoAction {
public :
TransformCanvasComponentsWorldUndoAction ( std : : vector < WorldUndoManager : : UndoObjectID > initUndoIDs , std : : vector < std : : pair < CoordSpaceHelper , CoordSpaceHelper > > initTransformData ) :
undoIDs ( std : : move ( initUndoIDs ) ) ,
transformData ( std : : move ( initTransformData ) )
{ }
std : : string get_name ( ) const override {
return " Transform Canvas Components " ;
}
bool undo ( WorldUndoManager & undoMan ) override {
std : : vector < NetworkingObjects : : NetObjID > toEditIDs ;
if ( ! undoMan . fill_netid_list_from_undoid_list ( toEditIDs , undoIDs ) )
return false ;
std : : vector < CanvasComponentContainer : : ObjInfo * > transformSet ;
for ( size_t i = 0 ; i < toEditIDs . size ( ) ; i + + ) {
auto objPtr = undoMan . world . netObjMan . get_obj_temporary_ref_from_id < CanvasComponentContainer > ( toEditIDs [ i ] ) ;
objPtr - > coords = transformData [ i ] . first ;
objPtr - > commit_transform ( undoMan . world . drawProg ) ;
transformSet . emplace_back ( & ( * objPtr - > objInfo ) ) ;
}
undoMan . world . drawProg . send_transforms_for ( transformSet ) ;
return true ;
}
bool redo ( WorldUndoManager & undoMan ) override {
std : : vector < NetworkingObjects : : NetObjID > toEditIDs ;
if ( ! undoMan . fill_netid_list_from_undoid_list ( toEditIDs , undoIDs ) )
return false ;
std : : vector < CanvasComponentContainer : : ObjInfo * > transformSet ;
for ( size_t i = 0 ; i < toEditIDs . size ( ) ; i + + ) {
auto objPtr = undoMan . world . netObjMan . get_obj_temporary_ref_from_id < CanvasComponentContainer > ( toEditIDs [ i ] ) ;
objPtr - > coords = transformData [ i ] . second ;
objPtr - > commit_transform ( undoMan . world . drawProg ) ;
transformSet . emplace_back ( & ( * objPtr - > objInfo ) ) ;
}
undoMan . world . drawProg . send_transforms_for ( transformSet ) ;
return true ;
}
2026-01-04 18:03:17 +03:00
void scale_up ( const WorldScalar & scaleUpAmount ) override {
for ( auto & [ oldTransform , newTransform ] : transformData ) {
oldTransform . scale_about ( WorldVec { 0 , 0 } , scaleUpAmount , true ) ;
newTransform . scale_about ( WorldVec { 0 , 0 } , scaleUpAmount , true ) ;
}
}
2026-01-04 17:44:12 +03:00
~ TransformCanvasComponentsWorldUndoAction ( ) { }
std : : vector < WorldUndoManager : : UndoObjectID > undoIDs ;
std : : vector < std : : pair < CoordSpaceHelper , CoordSpaceHelper > > transformData ;
} ;
drawP . world . undo . push ( std : : make_unique < TransformCanvasComponentsWorldUndoAction > ( std : : move ( undoIDList ) , std : : move ( transformDataList ) ) ) ;
2025-12-29 15:04:32 +03:00
}
2026-01-04 17:44:12 +03:00
2025-12-31 11:42:16 +03:00
drawP . send_transforms_for ( selectedSet ) ;
2025-12-29 15:04:32 +03:00
2026-01-01 08:34:49 +03:00
reset_transform_data ( ) ;
calculate_aabb ( ) ;
2025-12-29 15:04:32 +03:00
}
void DrawingProgramSelection : : update ( ) {
2026-03-02 16:36:34 +03:00
}
2025-12-29 15:04:32 +03:00
2026-03-02 16:36:34 +03:00
void DrawingProgramSelection : : input_key_callback_modify_selection ( const InputManager : : KeyCallbackArgs & key ) {
switch ( key . key ) {
2026-03-04 12:15:24 +03:00
case InputManager : : KEY_GENERIC_UP : {
translate_key ( InputManager : : KEY_GENERIC_UP , key . down ) ;
2026-03-02 16:36:34 +03:00
break ;
}
2026-03-04 12:15:24 +03:00
case InputManager : : KEY_GENERIC_DOWN : {
translate_key ( InputManager : : KEY_GENERIC_DOWN , key . down ) ;
2026-03-02 16:36:34 +03:00
break ;
}
2026-03-04 12:15:24 +03:00
case InputManager : : KEY_GENERIC_LEFT : {
translate_key ( InputManager : : KEY_GENERIC_LEFT , key . down ) ;
2026-03-02 16:36:34 +03:00
break ;
}
2026-03-04 12:15:24 +03:00
case InputManager : : KEY_GENERIC_RIGHT : {
translate_key ( InputManager : : KEY_GENERIC_RIGHT , key . down ) ;
2026-03-02 16:36:34 +03:00
break ;
}
}
}
2025-12-29 15:04:32 +03:00
2026-03-02 16:36:34 +03:00
void DrawingProgramSelection : : input_key_callback_display_selection ( const InputManager : : KeyCallbackArgs & key ) {
switch ( key . key ) {
case InputManager : : KEY_DRAW_DELETE : {
if ( key . down & & ! key . repeat )
delete_all ( ) ;
break ;
}
case InputManager : : KEY_COPY : {
if ( key . down & & ! key . repeat )
selection_to_clipboard ( ) ;
break ;
}
case InputManager : : KEY_CUT : {
if ( key . down & & ! key . repeat ) {
selection_to_clipboard ( ) ;
delete_all ( ) ;
}
break ;
}
case InputManager : : KEY_PASTE : {
if ( key . down & & ! key . repeat ) {
deselect_all ( ) ;
paste_clipboard ( drawP . world . main . input . mouse . pos ) ;
}
break ;
}
case InputManager : : KEY_PASTE_IMAGE : {
if ( key . down & & ! key . repeat ) {
deselect_all ( ) ;
paste_image ( drawP . world . main . input . mouse . pos ) ;
}
break ;
}
case InputManager : : KEY_DESELECT_AND_EDIT_TOOL : {
if ( key . down & & ! key . repeat ) {
if ( is_something_selected ( ) )
deselect_all ( ) ;
else
drawP . switch_to_tool ( DrawingProgramToolType : : EDIT , true ) ;
}
break ;
}
}
}
2025-12-29 15:04:32 +03:00
2026-03-02 16:36:34 +03:00
void DrawingProgramSelection : : input_mouse_button_on_canvas_callback_modify_selection ( const InputManager : : MouseButtonCallbackArgs & button ) {
2026-03-02 23:09:48 +03:00
if ( button . button = = InputManager : : MouseButton : : LEFT & & is_something_selected ( ) ) {
2026-03-02 16:36:34 +03:00
switch ( transformOpHappening ) {
case TransformOperation : : NONE : {
if ( button . down & & ! drawP . world . main . input . key ( InputManager : : KEY_GENERIC_LSHIFT ) . held & & ! drawP . world . main . input . key ( InputManager : : KEY_GENERIC_LALT ) . held ) {
if ( mouse_collided_with_scale_point ( ) ) {
scaleData . currentPos = scaleData . startPos = selectionTransformCoords . from_space_world ( initialSelectionAABB . max ) ;
scaleData . centerPos = selectionTransformCoords . from_space_world ( initialSelectionAABB . center ( ) ) ;
transformOpHappening = TransformOperation : : SCALE ;
check_add_stroke_color_change_undo ( ) ;
2025-12-29 15:04:32 +03:00
}
2026-03-02 16:36:34 +03:00
else if ( mouse_collided_with_rotate_center_handle_point ( ) ) {
transformOpHappening = TransformOperation : : ROTATE_RELOCATE_CENTER ;
check_add_stroke_color_change_undo ( ) ;
2025-12-29 15:04:32 +03:00
}
2026-03-02 16:36:34 +03:00
else if ( mouse_collided_with_rotate_handle_point ( ) ) {
rotateData . rotationAngle = 0.0 ;
transformOpHappening = TransformOperation : : ROTATE ;
check_add_stroke_color_change_undo ( ) ;
2025-12-29 15:04:32 +03:00
}
2026-03-02 16:36:34 +03:00
else if ( mouse_collided_with_selection_aabb ( ) ) {
2026-03-05 12:36:34 +03:00
translateData . startPos = drawP . world . drawData . cam . c . from_space ( button . pos ) ;
2026-03-02 16:36:34 +03:00
transformOpHappening = TransformOperation : : TRANSLATE ;
translateData . translateWithKeys = false ;
check_add_stroke_color_change_undo ( ) ;
2025-12-29 15:04:32 +03:00
}
2026-03-02 16:36:34 +03:00
}
break ;
}
case TransformOperation : : TRANSLATE :
case TransformOperation : : ROTATE :
case TransformOperation : : SCALE : {
if ( ! button . down )
commit_transform_selection ( ) ;
break ;
}
case TransformOperation : : ROTATE_RELOCATE_CENTER : {
if ( ! button . down )
transformOpHappening = TransformOperation : : NONE ;
break ;
2025-12-29 15:04:32 +03:00
}
}
2026-02-28 22:47:35 +03:00
}
}
2025-12-29 15:04:32 +03:00
2026-03-02 16:36:34 +03:00
void DrawingProgramSelection : : input_mouse_motion_callback_modify_selection ( const InputManager : : MouseMotionCallbackArgs & motion ) {
2026-03-02 23:09:48 +03:00
if ( drawP . world . main . input . mouse . leftDown & & is_something_selected ( ) ) {
2026-03-02 16:36:34 +03:00
rebuild_cam_space ( ) ;
switch ( transformOpHappening ) {
case TransformOperation : : NONE :
break ;
case TransformOperation : : TRANSLATE : {
if ( ! translateData . translateWithKeys )
selectionTransformCoords = CoordSpaceHelperTransform ( drawP . world . drawData . cam . c . from_space ( motion . pos ) - translateData . startPos ) ;
break ;
}
case TransformOperation : : SCALE : {
WorldVec centerToScaleStart = scaleData . startPos - scaleData . centerPos ;
Vector2f centerToScaleStartInCamSpace = drawP . world . drawData . cam . c . normalized_dir_to_space ( centerToScaleStart . normalized ( ) ) ;
Vector2f scaleCenterPointInCamSpace = drawP . world . drawData . cam . c . to_space ( scaleData . centerPos ) ;
scaleData . currentPos = drawP . world . drawData . cam . c . from_space ( project_point_on_line ( motion . pos , scaleCenterPointInCamSpace , ( scaleCenterPointInCamSpace + centerToScaleStartInCamSpace ) . eval ( ) ) ) ;
WorldVec centerToScaleCurrent = scaleData . currentPos - scaleData . centerPos ;
WorldScalar centerToScaleStartNorm = centerToScaleStart . norm ( ) ;
WorldScalar centerToScaleCurrentNorm = centerToScaleCurrent . norm ( ) ;
bool isAnyNumberZero = centerToScaleCurrentNorm = = WorldScalar ( 0 ) | | centerToScaleStartNorm = = WorldScalar ( 0 ) ;
if ( ! isAnyNumberZero ) {
WorldMultiplier scaleAmount = WorldMultiplier ( centerToScaleStartNorm ) / WorldMultiplier ( centerToScaleCurrentNorm ) ;
selectionTransformCoords = CoordSpaceHelperTransform ( scaleData . centerPos , scaleAmount ) ;
}
break ;
}
case TransformOperation : : ROTATE_RELOCATE_CENTER : {
rotateData . centerPos = drawP . world . drawData . cam . c . from_space ( motion . pos ) ;
break ;
}
case TransformOperation : : ROTATE : {
Vector2f rotationPointDiff = drawP . world . main . input . mouse . pos - rotateData . centerHandlePoint ;
rotateData . rotationAngle = std : : atan2 ( rotationPointDiff . y ( ) , rotationPointDiff . x ( ) ) ;
selectionTransformCoords = CoordSpaceHelperTransform ( rotateData . centerPos , rotateData . rotationAngle ) ;
break ;
}
2026-02-28 22:47:35 +03:00
}
2026-03-02 16:36:34 +03:00
}
2026-03-01 12:09:27 +03:00
}
void DrawingProgramSelection : : translate_key ( unsigned keyPressed , bool pressed ) {
2026-03-02 23:09:48 +03:00
if ( is_something_selected ( ) ) {
if ( pressed ) {
constexpr float KEY_TRANSLATE_MAGNITUDE = 5.0f ;
Vector2f moveVec ;
switch ( keyPressed ) {
2026-03-04 12:15:24 +03:00
case InputManager : : KEY_GENERIC_UP :
2026-03-02 23:09:48 +03:00
moveVec = { 0.0f , - 1.0f } ;
break ;
2026-03-04 12:15:24 +03:00
case InputManager : : KEY_GENERIC_DOWN :
2026-03-02 23:09:48 +03:00
moveVec = { 0.0f , 1.0f } ;
break ;
2026-03-04 12:15:24 +03:00
case InputManager : : KEY_GENERIC_RIGHT :
2026-03-02 23:09:48 +03:00
moveVec = { 1.0f , 0.0f } ;
break ;
2026-03-04 12:15:24 +03:00
case InputManager : : KEY_GENERIC_LEFT :
2026-03-02 23:09:48 +03:00
moveVec = { - 1.0f , 0.0f } ;
break ;
}
2026-03-01 12:09:27 +03:00
moveVec * = KEY_TRANSLATE_MAGNITUDE ;
2026-03-02 23:09:48 +03:00
if ( transformOpHappening = = TransformOperation : : NONE ) {
translateData . startPos = drawP . world . drawData . cam . c . dir_from_space ( moveVec ) ;
translateData . translateWithKeys = true ;
transformOpHappening = TransformOperation : : TRANSLATE ;
check_add_stroke_color_change_undo ( ) ;
}
else if ( transformOpHappening = = TransformOperation : : TRANSLATE & & translateData . translateWithKeys ) {
moveVec * = KEY_TRANSLATE_MAGNITUDE ;
translateData . startPos + = drawP . world . drawData . cam . c . dir_from_space ( moveVec ) ;
}
selectionTransformCoords = CoordSpaceHelperTransform ( translateData . startPos ) ;
2026-03-01 12:09:27 +03:00
}
2026-03-02 23:09:48 +03:00
else if ( transformOpHappening = = TransformOperation : : TRANSLATE & & translateData . translateWithKeys ) {
2026-03-04 12:15:24 +03:00
bool anyKeyHeld = drawP . world . main . input . key ( InputManager : : KEY_GENERIC_LEFT ) . held | | drawP . world . main . input . key ( InputManager : : KEY_GENERIC_RIGHT ) . held | | drawP . world . main . input . key ( InputManager : : KEY_GENERIC_DOWN ) . held | | drawP . world . main . input . key ( InputManager : : KEY_GENERIC_UP ) . held ;
2026-03-02 23:09:48 +03:00
if ( ! anyKeyHeld ) {
commit_transform_selection ( ) ;
return ;
}
2026-03-01 12:09:27 +03:00
}
}
2025-12-29 15:04:32 +03:00
}
void DrawingProgramSelection : : deselect_all ( ) {
2026-01-27 16:55:09 +03:00
check_add_stroke_color_change_undo ( ) ;
2025-12-29 15:04:32 +03:00
if ( is_something_selected ( ) ) {
2026-01-01 08:34:49 +03:00
commit_transform_selection ( ) ;
2025-12-29 15:04:32 +03:00
for ( auto & obj : selectedSet )
drawP . drawCache . add_component ( obj ) ;
reset_all ( ) ;
}
}
2026-01-20 11:59:03 +03:00
void DrawingProgramSelection : : push_selection_to_front ( ) {
2026-01-27 16:55:09 +03:00
check_add_stroke_color_change_undo ( ) ;
2026-01-20 11:59:03 +03:00
auto selectedVec = selectedSet ;
deselect_all ( ) ;
drawP . layerMan . push_components_to ( selectedVec , false ) ;
}
void DrawingProgramSelection : : push_selection_to_back ( ) {
2026-01-27 16:55:09 +03:00
check_add_stroke_color_change_undo ( ) ;
2026-01-20 11:59:03 +03:00
auto selectedVec = selectedSet ;
deselect_all ( ) ;
drawP . layerMan . push_components_to ( selectedVec , true ) ;
}
2025-12-29 15:04:32 +03:00
void DrawingProgramSelection : : delete_all ( ) {
2026-01-27 16:55:09 +03:00
check_add_stroke_color_change_undo ( ) ;
2025-12-29 15:04:32 +03:00
if ( is_something_selected ( ) ) {
auto selectedSetTemp = selectedSet ;
reset_all ( ) ; // Clear set before erasing, since calling erase_component_set will run a check to see if each object is selected and erase them one by one, which is slower
2025-12-31 10:06:26 +03:00
drawP . layerMan . erase_component_container ( selectedSetTemp ) ;
2025-12-29 15:04:32 +03:00
}
}
2025-12-30 19:46:08 +03:00
void DrawingProgramSelection : : erase_component ( CanvasComponentContainer : : ObjInfo * objToCheck ) {
2026-01-27 16:55:09 +03:00
check_add_stroke_color_change_undo ( ) ;
2026-01-01 08:34:49 +03:00
std : : erase ( selectedSet , objToCheck ) ;
if ( selectedSet . empty ( ) )
reset_all ( ) ;
2025-12-29 15:04:32 +03:00
}
2025-12-30 19:46:08 +03:00
CanvasComponentContainer : : ObjInfo * DrawingProgramSelection : : get_front_object_colliding_with_in_editing_layer ( const SCollision : : ColliderCollection < float > & cC ) {
2025-12-29 15:04:32 +03:00
auto cCWorld = drawP . world . drawData . cam . c . collider_to_world < SCollision : : ColliderCollection < WorldScalar > , SCollision : : ColliderCollection < float > > ( cC ) ;
2025-12-30 19:46:08 +03:00
CanvasComponentContainer : : ObjInfo * p = nullptr ;
2025-12-29 15:04:32 +03:00
for ( auto & c : selectedSet ) {
if ( drawP . layerMan . component_passes_layer_selector ( c , DrawingProgramLayerManager : : LayerSelector : : LAYER_BEING_EDITED ) & & ( ! p | | c - > pos > = p - > pos ) & & c - > obj - > collides_with ( drawP . world . drawData . cam . c , cCWorld , cC ) )
p = c ;
}
return p ;
}
void DrawingProgramSelection : : selection_to_clipboard ( ) {
2026-01-27 16:55:09 +03:00
check_add_stroke_color_change_undo ( ) ;
2026-02-28 22:47:35 +03:00
if ( is_something_selected ( ) ) {
auto & clipboard = drawP . world . main . clipboard ;
std : : unordered_set < NetworkingObjects : : NetObjID > resourceSet ;
for ( auto & c : selectedSet )
c - > obj - > get_comp ( ) . get_used_resources ( resourceSet ) ;
clipboard . components . clear ( ) ;
for ( auto & c : selectedSet )
clipboard . components . emplace_back ( c - > obj - > get_data_copy ( ) ) ;
clipboard . pos = initialSelectionAABB . center ( ) ;
clipboard . inverseScale = drawP . world . drawData . cam . c . inverseScale ;
clipboard . resources = drawP . world . rMan . copy_resource_set_to_map ( resourceSet ) ;
}
2025-12-29 15:04:32 +03:00
}
void DrawingProgramSelection : : paste_clipboard ( Vector2f pasteScreenPos ) {
2026-01-27 16:55:09 +03:00
check_add_stroke_color_change_undo ( ) ;
2025-12-29 19:57:36 +03:00
if ( drawP . layerMan . is_a_layer_being_edited ( ) ) {
auto & clipboard = drawP . world . main . clipboard ;
if ( ! drawP . is_selection_allowing_tool ( drawP . drawTool - > get_type ( ) ) )
drawP . switch_to_tool ( DrawingProgramToolType : : EDIT ) ;
2025-12-30 19:46:08 +03:00
std : : vector < std : : pair < CanvasComponentContainer : : ObjInfoIterator , CanvasComponentContainer * > > placedComponents ;
2025-12-29 19:57:36 +03:00
WorldVec mousePos = drawP . world . drawData . cam . c . from_space ( pasteScreenPos ) ;
WorldVec moveVec = drawP . world . main . clipboard . pos - mousePos ;
WorldMultiplier scaleMultiplier = WorldMultiplier ( drawP . world . main . clipboard . inverseScale ) / WorldMultiplier ( drawP . world . drawData . cam . c . inverseScale ) ;
WorldScalar scaleLimit ( 0.001 ) ;
for ( auto & c : drawP . world . main . clipboard . components ) {
if ( ( c - > coords . inverseScale / scaleMultiplier ) < scaleLimit ) {
Logger : : get ( ) . log ( " WORLDFATAL " , " Some pasted objects will be too small! Scale up the canvas first (zoom in alot) " ) ;
return ;
}
}
std : : unordered_map < NetworkingObjects : : NetObjID , NetworkingObjects : : NetObjID > resourceRemapIDs ;
for ( auto & r : clipboard . resources )
resourceRemapIDs [ r . first ] = drawP . world . rMan . add_resource ( r . second ) . get_net_id ( ) ;
for ( auto & c : clipboard . components ) {
CanvasComponentContainer * newComponentContainer = new CanvasComponentContainer ( drawP . world . netObjMan , * c ) ;
newComponentContainer - > get_comp ( ) . remap_resource_ids ( resourceRemapIDs ) ;
2025-12-29 20:11:24 +03:00
newComponentContainer - > coords . translate ( - moveVec ) ;
newComponentContainer - > coords . scale_about ( mousePos , scaleMultiplier ) ;
2025-12-30 19:46:08 +03:00
placedComponents . emplace_back ( drawP . layerMan . get_edited_layer_end_iterator ( ) , newComponentContainer ) ;
2025-12-29 19:57:36 +03:00
}
2026-01-08 14:02:37 +03:00
std : : vector < CanvasComponentContainer : : ObjInfoIterator > newlyInsertedObjectIts ;
drawP . layerMan . disable_add_to_cache_and_commit_update_block ( [ & ] ( ) {
newlyInsertedObjectIts = drawP . layerMan . add_many_components_to_layer_being_edited ( placedComponents ) ;
} ) ;
2025-12-31 10:06:26 +03:00
std : : vector < CanvasComponentContainer : : ObjInfo * > compSetInserted ;
2026-01-08 14:02:37 +03:00
for ( auto & it : newlyInsertedObjectIts )
2025-12-31 10:06:26 +03:00
compSetInserted . emplace_back ( & ( * it ) ) ;
2026-01-08 14:02:37 +03:00
parallel_loop_container ( compSetInserted , [ & drawP = drawP ] ( CanvasComponentContainer : : ObjInfo * comp ) {
comp - > obj - > commit_update_dont_invalidate_cache ( drawP ) ;
} ) ;
2025-12-29 19:57:36 +03:00
set_to_selection ( compSetInserted ) ;
2026-01-06 19:06:44 +03:00
}
}
void DrawingProgramSelection : : paste_image ( Vector2f pasteScreenPos ) {
2026-01-27 16:55:09 +03:00
check_add_stroke_color_change_undo ( ) ;
2026-01-06 19:06:44 +03:00
if ( drawP . layerMan . is_a_layer_being_edited ( ) ) {
# ifdef __EMSCRIPTEN__
struct PasteData {
std : : weak_ptr < World > w ;
Vector2f screenPos ;
} ;
static PasteData pasteData ;
pasteData . screenPos = pasteScreenPos ;
pasteData . w = make_weak_ptr ( drawP . world . main . world ) ;
emscripten_browser_clipboard : : paste_async_image ( [ ] ( std : : string_view pasteData , void * callbackData ) {
PasteData * p = ( PasteData * ) callbackData ;
std : : shared_ptr < World > wLock = p - > w . lock ( ) ;
if ( wLock )
wLock - > drawProg . add_file_to_canvas_by_data ( " Image from clipboard " , pasteData , p - > screenPos ) ;
else
Logger : : get ( ) . log ( " INFO " , " Loading image to canvas that has been destroyed " ) ;
} , & pasteData ) ;
# else
if ( ! drawP . is_selection_allowing_tool ( drawP . drawTool - > get_type ( ) ) )
drawP . switch_to_tool ( DrawingProgramToolType : : EDIT ) ;
std : : vector < CanvasComponentContainer : : ObjInfo * > compSetInserted ;
drawP . world . main . input . get_clipboard_image_data_SDL ( [ & ] ( std : : string_view pasteData ) {
compSetInserted . emplace_back ( drawP . add_file_to_canvas_by_data ( " Image from clipboard " , pasteData , pasteScreenPos ) ) ;
} ) ;
if ( ! compSetInserted . empty ( ) & & compSetInserted . back ( ) ! = nullptr ) {
drawP . drawCache . erase_component ( compSetInserted . back ( ) ) ;
set_to_selection ( compSetInserted ) ;
}
# endif
2025-12-29 19:57:36 +03:00
}
2025-12-29 15:04:32 +03:00
}
bool DrawingProgramSelection : : is_being_transformed ( ) {
return transformOpHappening ! = TransformOperation : : NONE ;
}
2026-01-05 15:37:21 +03:00
bool DrawingProgramSelection : : is_empty_transform ( ) {
return transformOpHappening = = TransformOperation : : NONE | | selectionTransformCoords . is_identity ( ) ;
}
2025-12-31 10:06:26 +03:00
std : : unordered_set < CanvasComponentContainer : : ObjInfo * > DrawingProgramSelection : : get_selection_as_set ( ) {
return std : : unordered_set < CanvasComponentContainer : : ObjInfo * > ( selectedSet . begin ( ) , selectedSet . end ( ) ) ;
2025-12-29 15:04:32 +03:00
}
void DrawingProgramSelection : : rebuild_cam_space ( ) {
2026-03-01 12:09:27 +03:00
selectionRectPoints [ 0 ] = selectionTransformCoords . from_space_world ( initialSelectionAABB . min ) ;
selectionRectPoints [ 1 ] = selectionTransformCoords . from_space_world ( initialSelectionAABB . bottom_left ( ) ) ;
selectionRectPoints [ 2 ] = selectionTransformCoords . from_space_world ( initialSelectionAABB . max ) ;
selectionRectPoints [ 3 ] = selectionTransformCoords . from_space_world ( initialSelectionAABB . top_right ( ) ) ;
2026-03-02 16:36:34 +03:00
scaleData . handlePoint = drawP . world . drawData . cam . c . to_space ( selectionRectPoints [ 2 ] ) ;
2026-03-01 12:09:27 +03:00
rotateData . centerHandlePoint = drawP . world . drawData . cam . c . to_space ( rotateData . centerPos ) ;
rotateData . handlePoint = get_rotation_point_pos_from_angle ( rotateData . rotationAngle ) ;
2025-12-29 15:04:32 +03:00
SCollision : : ColliderCollection < WorldScalar > collideRectWorld ;
collideRectWorld . triangle . emplace_back ( selectionRectPoints [ 0 ] , selectionRectPoints [ 1 ] , selectionRectPoints [ 2 ] ) ;
collideRectWorld . triangle . emplace_back ( selectionRectPoints [ 0 ] , selectionRectPoints [ 2 ] , selectionRectPoints [ 3 ] ) ;
collideRectWorld . recalculate_bounds ( ) ;
camSpaceSelection = drawP . world . drawData . cam . c . world_collider_to_coords < SCollision : : ColliderCollection < float > > ( collideRectWorld ) ;
}
Vector2f DrawingProgramSelection : : get_rotation_point_pos_from_angle ( double angle ) {
return Vector2f { rotateData . centerHandlePoint + ROTATION_POINTS_DISTANCE * drawP . world . main . toolbar . final_gui_scale ( ) * Vector2f { std : : cos ( angle ) , std : : sin ( angle ) } } ;
}
void DrawingProgramSelection : : draw_components ( SkCanvas * canvas , const DrawData & drawData ) {
if ( is_something_selected ( ) ) {
rebuild_cam_space ( ) ;
DrawData selectionDrawData = drawData ;
selectionDrawData . cam . c = selectionTransformCoords . other_coord_space_to_this_space ( selectionDrawData . cam . c ) ;
selectionDrawData . cam . set_viewing_area ( drawP . world . main . window . size . cast < float > ( ) ) ;
selectionDrawData . refresh_draw_optimizing_values ( ) ;
2025-12-31 10:06:26 +03:00
for ( auto & c : selectedSet )
c - > obj - > draw ( canvas , selectionDrawData ) ;
2025-12-29 15:04:32 +03:00
}
}
void DrawingProgramSelection : : draw_gui ( SkCanvas * canvas , const DrawData & drawData ) {
if ( is_something_selected ( ) & & ! camSpaceSelection . triangle . empty ( ) ) {
SkPathBuilder selectionRectPath ;
selectionRectPath . moveTo ( convert_vec2 < SkPoint > ( camSpaceSelection . triangle [ 0 ] . p [ 0 ] ) ) ;
selectionRectPath . lineTo ( convert_vec2 < SkPoint > ( camSpaceSelection . triangle [ 0 ] . p [ 1 ] ) ) ;
selectionRectPath . lineTo ( convert_vec2 < SkPoint > ( camSpaceSelection . triangle [ 0 ] . p [ 2 ] ) ) ;
selectionRectPath . lineTo ( convert_vec2 < SkPoint > ( camSpaceSelection . triangle [ 1 ] . p [ 2 ] ) ) ;
selectionRectPath . close ( ) ;
SkPaint p { SkColor4f { 0.3f , 0.6f , 0.9f , 0.4f } } ;
canvas - > drawPath ( selectionRectPath . detach ( ) , p ) ;
if ( drawP . is_actual_selection_tool ( drawP . drawTool - > get_type ( ) ) ) {
if ( transformOpHappening = = TransformOperation : : NONE | | transformOpHappening = = TransformOperation : : ROTATE | | transformOpHappening = = TransformOperation : : ROTATE_RELOCATE_CENTER )
drawP . draw_drag_circle ( canvas , rotateData . centerHandlePoint , { 0.9f , 0.5f , 0.1f , 1.0f } , drawData ) ;
if ( transformOpHappening = = TransformOperation : : NONE | | transformOpHappening = = TransformOperation : : ROTATE )
drawP . draw_drag_circle ( canvas , rotateData . handlePoint , { 0.9f , 0.5f , 0.1f , 1.0f } , drawData , ROTATION_POINT_RADIUS_MULTIPLIER ) ;
if ( transformOpHappening = = TransformOperation : : NONE | | transformOpHappening = = TransformOperation : : SCALE )
drawP . draw_drag_circle ( canvas , scaleData . handlePoint , { 0.1f , 0.9f , 0.9f , 1.0f } , drawData ) ;
}
}
}