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
2025-12-29 15:04:32 +03:00
DrawingProgramSelection : : DrawingProgramSelection ( DrawingProgram & initDrawP ) :
drawP ( initDrawP )
{ }
void DrawingProgramSelection : : add_from_cam_coord_collider_to_selection ( const SCollision : : ColliderCollection < float > & cC , DrawingProgramLayerManager : : LayerSelector layerSelector , bool frontObjectOnly ) {
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 ) {
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 ( ) {
selectedSet . clear ( ) ;
2026-01-01 08:34:49 +03:00
reset_transform_data ( ) ;
}
void DrawingProgramSelection : : reset_transform_data ( ) {
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-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 ( ) {
if ( is_something_selected ( ) ) {
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 ( ) ) ;
scaleData . handlePoint = drawP . world . drawData . cam . c . to_space ( initialSelectionAABB . max ) ;
rotateData . centerHandlePoint = drawP . world . drawData . cam . c . to_space ( rotateData . centerPos ) ;
rotateData . handlePoint = get_rotation_point_pos_from_angle ( rotateData . rotationAngle ) ;
rebuild_cam_space ( ) ;
2026-01-23 09:37:57 +03:00
constexpr float KEY_TRANSLATE_MAGNITUDE = 5.0f ;
2025-12-29 15:04:32 +03:00
if ( drawP . is_actual_selection_tool ( drawP . drawTool - > get_type ( ) ) ) {
switch ( transformOpHappening ) {
case TransformOperation : : NONE :
if ( drawP . controls . leftClick & & ! 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 ;
}
else if ( mouse_collided_with_rotate_center_handle_point ( ) )
transformOpHappening = TransformOperation : : ROTATE_RELOCATE_CENTER ;
else if ( mouse_collided_with_rotate_handle_point ( ) ) {
rotateData . rotationAngle = 0.0 ;
transformOpHappening = TransformOperation : : ROTATE ;
}
else if ( mouse_collided_with_selection_aabb ( ) ) {
translateData . startPos = drawP . world . get_mouse_world_pos ( ) ;
transformOpHappening = TransformOperation : : TRANSLATE ;
2026-01-23 09:37:57 +03:00
translateData . translateWithKeys = false ;
}
}
else {
Vector2f moveVec = { 0 , 0 } ;
if ( drawP . world . main . input . key ( InputManager : : KEY_TEXT_UP ) . pressed )
moveVec . y ( ) - = 1.0f ;
if ( drawP . world . main . input . key ( InputManager : : KEY_TEXT_DOWN ) . pressed )
moveVec . y ( ) + = 1.0f ;
if ( drawP . world . main . input . key ( InputManager : : KEY_TEXT_RIGHT ) . pressed )
moveVec . x ( ) + = 1.0f ;
if ( drawP . world . main . input . key ( InputManager : : KEY_TEXT_LEFT ) . pressed )
moveVec . x ( ) - = 1.0f ;
if ( moveVec ! = Vector2f { 0.0f , 0.0f } ) {
moveVec * = KEY_TRANSLATE_MAGNITUDE ;
translateData . startPos = drawP . world . drawData . cam . c . dir_from_space ( moveVec ) ;
translateData . translateWithKeys = true ;
selectionTransformCoords = CoordSpaceHelperTransform ( translateData . startPos ) ;
transformOpHappening = TransformOperation : : TRANSLATE ;
translateData . keyTranslateLastMoveTime = std : : chrono : : steady_clock : : now ( ) ;
2025-12-29 15:04:32 +03:00
}
}
break ;
case TransformOperation : : TRANSLATE :
2026-01-23 09:37:57 +03:00
if ( translateData . translateWithKeys ) {
Vector2f moveVec = { 0 , 0 } ;
constexpr auto TIME_TO_NEXT_MOVE = std : : chrono : : milliseconds ( 700 ) ;
constexpr auto TIME_TO_SUBTRACT_BETWEEN_REPEATS = std : : chrono : : milliseconds ( 650 ) ;
if ( drawP . world . main . input . key ( InputManager : : KEY_TEXT_UP ) . held )
moveVec . y ( ) - = 1.0f ;
if ( drawP . world . main . input . key ( InputManager : : KEY_TEXT_DOWN ) . held )
moveVec . y ( ) + = 1.0f ;
if ( drawP . world . main . input . key ( InputManager : : KEY_TEXT_RIGHT ) . held )
moveVec . x ( ) + = 1.0f ;
if ( drawP . world . main . input . key ( InputManager : : KEY_TEXT_LEFT ) . held )
moveVec . x ( ) - = 1.0f ;
if ( moveVec ! = Vector2f { 0.0f , 0.0f } & & ( std : : chrono : : steady_clock : : now ( ) - translateData . keyTranslateLastMoveTime ) > = TIME_TO_NEXT_MOVE ) {
moveVec * = KEY_TRANSLATE_MAGNITUDE ;
translateData . startPos + = drawP . world . drawData . cam . c . dir_from_space ( moveVec ) ;
selectionTransformCoords = CoordSpaceHelperTransform ( translateData . startPos ) ;
translateData . keyTranslateLastMoveTime = std : : chrono : : steady_clock : : now ( ) - TIME_TO_SUBTRACT_BETWEEN_REPEATS ; // Time between repeat moves is shorter than time between first press and first repeat
}
bool anyKeyHeld = drawP . world . main . input . key ( InputManager : : KEY_TEXT_LEFT ) . held | | drawP . world . main . input . key ( InputManager : : KEY_TEXT_RIGHT ) . held | | drawP . world . main . input . key ( InputManager : : KEY_TEXT_DOWN ) . held | | drawP . world . main . input . key ( InputManager : : KEY_TEXT_UP ) . held ;
if ( ! anyKeyHeld ) {
commit_transform_selection ( ) ;
return ;
}
}
2025-12-29 15:04:32 +03:00
else {
2026-01-23 09:37:57 +03:00
if ( drawP . controls . leftClickHeld )
selectionTransformCoords = CoordSpaceHelperTransform ( drawP . world . get_mouse_world_pos ( ) - translateData . startPos ) ;
else {
commit_transform_selection ( ) ;
return ;
}
2025-12-29 15:04:32 +03:00
}
break ;
case TransformOperation : : SCALE :
if ( drawP . controls . leftClickHeld ) {
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 ( drawP . world . main . input . mouse . 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 ) ;
}
}
else {
commit_transform_selection ( ) ;
return ;
}
break ;
case TransformOperation : : ROTATE_RELOCATE_CENTER :
if ( drawP . controls . leftClickHeld )
rotateData . centerPos = drawP . world . get_mouse_world_pos ( ) ;
else
transformOpHappening = TransformOperation : : NONE ;
break ;
case TransformOperation : : ROTATE :
if ( drawP . controls . leftClickHeld ) {
Vector2f rotationPointDiff = drawP . world . main . input . mouse . pos - rotateData . centerHandlePoint ;
rotateData . rotationAngle = std : : atan2 ( rotationPointDiff . y ( ) , rotationPointDiff . x ( ) ) ;
selectionTransformCoords = CoordSpaceHelperTransform ( rotateData . centerPos , rotateData . rotationAngle ) ;
}
else {
commit_transform_selection ( ) ;
return ;
}
break ;
}
}
else if ( transformOpHappening ! = TransformOperation : : NONE ) {
commit_transform_selection ( ) ;
return ;
}
if ( drawP . world . main . input . key ( InputManager : : KEY_GENERIC_ESCAPE ) . pressed )
deselect_all ( ) ;
else if ( drawP . world . main . input . key ( InputManager : : KEY_DRAW_DELETE ) . pressed )
delete_all ( ) ;
else if ( drawP . world . main . input . key ( InputManager : : KEY_COPY ) . pressed & & ! is_being_transformed ( ) )
selection_to_clipboard ( ) ;
else if ( drawP . world . main . input . key ( InputManager : : KEY_CUT ) . pressed & & ! is_being_transformed ( ) ) {
selection_to_clipboard ( ) ;
delete_all ( ) ;
}
else if ( drawP . world . main . input . key ( InputManager : : KEY_PASTE ) . pressed ) {
deselect_all ( ) ;
paste_clipboard ( drawP . world . main . input . mouse . pos ) ;
}
2026-01-06 19:06:44 +03:00
else if ( drawP . world . main . input . key ( InputManager : : KEY_PASTE_IMAGE ) . pressed ) {
deselect_all ( ) ;
paste_image ( drawP . world . main . input . mouse . pos ) ;
}
2025-12-29 15:04:32 +03:00
}
else if ( drawP . world . main . input . key ( InputManager : : KEY_PASTE ) . pressed & & ! drawP . prevent_undo_or_redo ( ) )
paste_clipboard ( drawP . world . main . input . mouse . pos ) ;
2026-01-06 19:06:44 +03:00
else if ( drawP . world . main . input . key ( InputManager : : KEY_PASTE_IMAGE ) . pressed & & ! drawP . prevent_undo_or_redo ( ) )
paste_image ( drawP . world . main . input . mouse . pos ) ;
2025-12-29 15:04:32 +03:00
else if ( drawP . world . main . input . key ( InputManager : : KEY_GENERIC_ESCAPE ) . pressed )
drawP . switch_to_tool ( DrawingProgramToolType : : EDIT , true ) ;
}
void DrawingProgramSelection : : deselect_all ( ) {
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 ( ) {
auto selectedVec = selectedSet ;
deselect_all ( ) ;
drawP . layerMan . push_components_to ( selectedVec , false ) ;
}
void DrawingProgramSelection : : push_selection_to_back ( ) {
auto selectedVec = selectedSet ;
deselect_all ( ) ;
drawP . layerMan . push_components_to ( selectedVec , true ) ;
}
2025-12-29 15:04:32 +03:00
void DrawingProgramSelection : : delete_all ( ) {
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-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 ( ) {
2025-12-29 19:57:36 +03:00
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 ( ) ;
2025-12-31 10:06:26 +03:00
for ( auto & c : selectedSet )
2025-12-29 19:57:36 +03:00
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 ) {
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 ) {
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 ( ) {
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 ( ) ) {
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 ( ) ) ;
scaleData . handlePoint = drawP . world . drawData . cam . c . to_space ( selectionTransformCoords . from_space_world ( initialSelectionAABB . max ) ) ;
rotateData . centerHandlePoint = drawP . world . drawData . cam . c . to_space ( selectionTransformCoords . from_space_world ( rotateData . centerPos ) ) ;
rotateData . handlePoint = get_rotation_point_pos_from_angle ( rotateData . rotationAngle ) ;
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 ( ) ;
SkImageInfo imgInfo = canvas - > imageInfo ( ) ;
sk_sp < SkSurface > surface ;
# ifdef USE_SKIA_BACKEND_GRAPHITE
surface = SkSurfaces : : RenderTarget ( drawP . world . main . window . recorder ( ) , imgInfo , skgpu : : Mipmapped : : kNo , drawP . world . main . window . defaultMSAASurfaceProps ) ;
# elif USE_SKIA_BACKEND_GANESH
surface = SkSurfaces : : RenderTarget ( drawP . world . main . window . ctx . get ( ) , skgpu : : Budgeted : : kNo , imgInfo , drawP . world . main . window . defaultMSAASampleCount , & drawP . world . main . window . defaultMSAASurfaceProps ) ;
# endif
if ( ! surface )
throw std : : runtime_error ( " [DrawingProgramSelection::draw_components] Could not make temporary surface " ) ;
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
SkPaint glowBlurP ;
glowBlurP . setImageFilter ( SkImageFilters : : Blur ( 5 , 5 , nullptr ) ) ;
canvas - > drawImage ( surface - > makeTemporaryImage ( ) , 0 , 0 , SkSamplingOptions { SkFilterMode : : kLinear } , & glowBlurP ) ;
canvas - > drawImage ( surface - > makeTemporaryImage ( ) , 0 , 0 , SkSamplingOptions { SkFilterMode : : kLinear } , nullptr ) ;
}
}
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 ) ;
}
}
}