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
2026-03-29 20:23:38 +03:00
# include "../GUIStuff/Elements/LayoutElement.hpp"
# include "../GUIStuff/Elements/RotateWheel.hpp"
# include "../GUIStuff/Elements/PositionAdjustingPopupMenu.hpp"
# include "../GUIStuff/ElementHelpers/ButtonHelpers.hpp"
# include "../GUIStuff/ElementHelpers/TextLabelHelpers.hpp"
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 ;
}
2026-03-04 11:03:05 +03:00
void DrawingProgram : : input_drop_file_callback ( const InputManager : : DropCallbackArgs & drop ) {
2026-03-30 17:05:24 +03:00
if ( std : : filesystem : : is_regular_file ( drop . data ) ) {
2026-03-04 11:03:05 +03:00
# ifdef __EMSCRIPTEN_
add_file_to_canvas_by_path ( drop . data , world . main . input . mouse . pos , true ) ;
# else
add_file_to_canvas_by_path ( drop . data , drop . pos , true ) ;
# endif
}
}
void DrawingProgram : : input_drop_text_callback ( const InputManager : : DropCallbackArgs & drop ) {
2026-03-30 17:05:24 +03:00
if ( is_valid_http_url ( drop . data ) ) {
2026-03-04 11:03:05 +03:00
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 = drop . pos - imDim ;
img . d . p2 = drop . pos + imDim ;
img . d . imageID = { 0 , 0 } ;
auto newObjInfo = layerMan . add_component_to_layer_being_edited ( newContainer ) ;
layerMan . add_undo_place_component ( newObjInfo ) ;
droppedDownloadingFiles . emplace_back ( newObjInfo , world . main . window . size . cast < float > ( ) , FileDownloader : : download_data_from_url ( drop . data ) ) ;
}
}
2026-03-02 16:36:34 +03:00
void DrawingProgram : : input_mouse_button_callback ( const InputManager : : MouseButtonCallbackArgs & button ) {
2026-03-03 22:31:48 +03:00
auto buttonCallbacks = [ & ] ( const InputManager : : MouseButtonCallbackArgs & b ) {
drawTool - > input_mouse_button_on_canvas_callback ( b ) ;
} ;
2026-03-30 17:05:24 +03:00
if ( button . down ) {
2026-03-02 23:09:48 +03:00
if ( button . button = = InputManager : : MouseButton : : RIGHT ) {
2026-03-03 22:31:48 +03:00
if ( ! controls . middleClickHeld & & ! controls . leftClickHeld ) {
2026-03-02 16:36:34 +03:00
if ( world . main . toolbar . rightClickPopupLocation )
world . main . toolbar . rightClickPopupLocation = std : : nullopt ;
else
2026-03-31 13:33:15 +03:00
world . main . toolbar . rightClickPopupLocation = world . main . input . mouse . pos / world . main . g . final_gui_scale ( ) ;
2026-03-02 16:36:34 +03:00
}
2026-02-28 22:47:35 +03:00
}
2026-03-02 16:36:34 +03:00
else {
2026-03-03 22:31:48 +03:00
world . main . toolbar . rightClickPopupLocation = std : : nullopt ;
if ( button . button = = InputManager : : MouseButton : : LEFT & & ! controls . middleClickHeld ) {
controls . leftClickHeld = true ;
buttonCallbacks ( button ) ;
}
else if ( button . button = = InputManager : : MouseButton : : MIDDLE ) {
if ( controls . leftClickHeld ) {
controls . leftClickHeld = false ;
InputManager : : MouseButtonCallbackArgs leftReleaseCallback ;
leftReleaseCallback . clicks = 0 ;
leftReleaseCallback . down = false ;
leftReleaseCallback . pos = button . pos ;
leftReleaseCallback . button = InputManager : : MouseButton : : LEFT ;
buttonCallbacks ( leftReleaseCallback ) ;
}
controls . middleClickHeld = true ;
buttonCallbacks ( button ) ;
}
2026-02-28 22:47:35 +03:00
}
2026-03-02 16:36:34 +03:00
}
2026-03-03 22:31:48 +03:00
else if ( ! button . down ) {
if ( controls . leftClickHeld & & button . button = = InputManager : : MouseButton : : LEFT ) {
controls . leftClickHeld = false ;
buttonCallbacks ( button ) ;
}
else if ( controls . middleClickHeld & & button . button = = InputManager : : MouseButton : : MIDDLE ) {
controls . middleClickHeld = false ;
buttonCallbacks ( button ) ;
}
2026-03-02 16:36:34 +03:00
}
2026-03-03 10:18:47 +03:00
if ( toolToSwitchToAfterUpdate ) {
switch_to_tool_ptr ( std : : move ( toolToSwitchToAfterUpdate ) ) ;
toolToSwitchToAfterUpdate = nullptr ;
}
2026-03-02 16:36:34 +03:00
}
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-03 16:01:21 +03:00
pen_tool_switch_check ( ) ;
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 ;
2026-03-03 16:01:21 +03:00
pen_tool_switch_check ( ) ;
2026-03-02 16:36:34 +03:00
}
break ;
}
}
2026-03-04 12:15:24 +03:00
selection . input_key_callback_display_selection ( key ) ;
2026-03-02 16:36:34 +03:00
drawTool - > input_key_callback ( key ) ;
2026-02-28 22:47:35 +03:00
}
2026-03-03 16:01:21 +03:00
void DrawingProgram : : input_pen_button_callback ( const InputManager : : PenButtonCallbackArgs & button ) {
pen_tool_switch_check ( ) ;
drawTool - > input_pen_button_callback ( button ) ;
}
void DrawingProgram : : input_pen_touch_callback ( const InputManager : : PenTouchCallbackArgs & touch ) {
pen_tool_switch_check ( ) ;
drawTool - > input_pen_touch_callback ( touch ) ;
}
void DrawingProgram : : input_pen_motion_callback ( const InputManager : : PenMotionCallbackArgs & motion ) {
pen_tool_switch_check ( ) ;
drawTool - > input_pen_motion_callback ( motion ) ;
}
void DrawingProgram : : input_pen_axis_callback ( const InputManager : : PenAxisCallbackArgs & axis ) {
pen_tool_switch_check ( ) ;
drawTool - > input_pen_axis_callback ( axis ) ;
}
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 ( ) {
2026-03-29 20:23:38 +03:00
using namespace GUIStuff ;
using namespace ElementHelpers ;
2026-03-31 13:17:58 +03:00
GUIManager & gui = world . main . g . gui ;
auto & io = gui . io ;
gui . new_id ( " Drawing Program Toolbar GUI " , [ & ] {
2026-03-31 17:33:09 +03:00
gui . element < LayoutElement > ( " Drawing Program Toolbar GUI " , [ & ] ( const Clay_ElementId & lId ) {
CLAY ( lId , {
2026-03-30 18:51:14 +03:00
. layout = {
. sizing = { . width = CLAY_SIZING_FIT ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
2026-03-31 13:17:58 +03:00
. padding = CLAY_PADDING_ALL ( static_cast < uint16_t > ( io . theme - > padding1 / 2 ) ) ,
. childGap = static_cast < uint16_t > ( io . theme - > childGap1 / 2 ) ,
2026-03-30 18:51:14 +03:00
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
2026-03-31 13:17:58 +03:00
. backgroundColor = convert_vec4 < Clay_Color > ( io . theme - > backColor1 ) ,
. cornerRadius = CLAY_CORNER_RADIUS ( io . theme - > windowCorners1 )
2026-03-30 18:51:14 +03:00
} ) {
auto tool_button = [ & ] ( const char * id , const std : : string & svgPath , DrawingProgramToolType toolType ) {
2026-03-31 13:17:58 +03:00
svg_icon_button ( gui , id , svgPath , {
2026-03-30 18:51:14 +03:00
. drawType = SelectableButton : : DrawType : : TRANSPARENT_ALL ,
. isSelected = drawTool - > get_type ( ) = = toolType ,
. onClick = [ & , toolType ] {
switch_to_tool ( toolType ) ;
}
} ) ;
} ;
tool_button ( " Brush Toolbar Button " , " data/icons/brush.svg " , DrawingProgramToolType : : BRUSH ) ;
tool_button ( " Eraser Toolbar Button " , " data/icons/eraser.svg " , DrawingProgramToolType : : ERASER ) ;
tool_button ( " Line Toolbar Button " , " data/icons/line.svg " , DrawingProgramToolType : : LINE ) ;
tool_button ( " Text Toolbar Button " , " data/icons/text.svg " , DrawingProgramToolType : : TEXTBOX ) ;
tool_button ( " Ellipse Toolbar Button " , " data/icons/circle.svg " , DrawingProgramToolType : : ELLIPSE ) ;
tool_button ( " Rect Toolbar Button " , " data/icons/rectangle.svg " , DrawingProgramToolType : : RECTANGLE ) ;
tool_button ( " RectSelect Toolbar Button " , " data/icons/rectselect.svg " , DrawingProgramToolType : : RECTSELECT ) ;
tool_button ( " LassoSelect Toolbar Button " , " data/icons/lassoselect.svg " , DrawingProgramToolType : : LASSOSELECT ) ;
tool_button ( " Edit Toolbar Button " , " data/icons/cursor.svg " , DrawingProgramToolType : : EDIT ) ;
tool_button ( " Eyedropper Toolbar Button " , " data/icons/eyedropper.svg " , DrawingProgramToolType : : EYEDROPPER ) ;
tool_button ( " Zoom Canvas Toolbar Button " , " data/icons/zoom.svg " , DrawingProgramToolType : : ZOOM ) ;
tool_button ( " Pan Canvas Toolbar Button " , " data/icons/hand.svg " , DrawingProgramToolType : : PAN ) ;
2026-03-31 20:52:46 +03:00
std : : shared_ptr < double > newRotationAngle = std : : make_shared < double > ( world . drawData . cam . c . rotation ) ;
gui . element < RotateWheel > ( " Canvas Rotate Wheel " , newRotationAngle . get ( ) , [ & , newRotationAngle ] {
world . drawData . cam . c . rotate_about ( world . drawData . cam . c . from_space ( world . main . window . size . cast < float > ( ) * 0.5f ) , * newRotationAngle - world . drawData . cam . c . rotation ) ;
* newRotationAngle = world . drawData . cam . c . rotation ;
2026-03-29 20:23:38 +03:00
} ) ;
2026-03-30 18:51:14 +03:00
}
} ) ;
2026-03-29 20:23:38 +03:00
} ) ;
}
void DrawingProgram : : popup_menu_action_button ( const char * id , const char * text , const std : : function < void ( ) > & onClick ) {
2026-03-31 13:17:58 +03:00
GUIStuff : : GUIManager & gui = world . main . g . gui ;
GUIStuff : : ElementHelpers : : text_button ( gui , id , text , {
2026-03-29 20:23:38 +03:00
. drawType = GUIStuff : : SelectableButton : : DrawType : : TRANSPARENT_ALL ,
. wide = true ,
. onClick = onClick
} ) ;
2025-12-01 17:33:14 +03:00
}
2025-12-01 18:12:22 +03:00
// Should only be used in selection allowing tools
2026-03-29 20:23:38 +03:00
void DrawingProgram : : selection_action_menu ( Vector2f popupPos ) {
using namespace GUIStuff ;
using namespace ElementHelpers ;
2025-12-01 17:33:14 +03:00
Toolbar & t = world . main . toolbar ;
2026-03-31 13:17:58 +03:00
GUIStuff : : GUIManager & gui = world . main . g . gui ;
auto & io = gui . io ;
gui . element < PositionAdjustingPopupMenu > ( " Selection popup menu " , popupPos , [ & , popupPos ] {
text_label_light ( gui , " Selection menu " ) ;
2026-03-29 20:23:38 +03:00
popup_menu_action_button ( " Paste " , " Paste " , [ & ] {
2025-12-29 15:04:32 +03:00
selection . deselect_all ( ) ;
2026-03-31 13:33:15 +03:00
selection . paste_clipboard ( popupPos * world . main . g . final_gui_scale ( ) ) ;
2026-03-29 20:23:38 +03:00
} ) ;
popup_menu_action_button ( " Paste Image " , " Paste Image " , [ & ] {
2026-01-06 19:06:44 +03:00
selection . deselect_all ( ) ;
2026-03-31 13:33:15 +03:00
selection . paste_image ( popupPos * world . main . g . final_gui_scale ( ) ) ;
2026-03-29 20:23:38 +03:00
} ) ;
2025-12-29 15:04:32 +03:00
if ( selection . is_something_selected ( ) ) {
2026-03-29 20:23:38 +03:00
popup_menu_action_button ( " Copy " , " Copy " , [ & ] {
2025-12-29 15:04:32 +03:00
selection . selection_to_clipboard ( ) ;
2026-03-29 20:23:38 +03:00
} ) ;
popup_menu_action_button ( " Cut " , " Cut " , [ & ] {
2025-12-29 15:04:32 +03:00
selection . selection_to_clipboard ( ) ;
selection . delete_all ( ) ;
2026-03-29 20:23:38 +03:00
} ) ;
popup_menu_action_button ( " Delete " , " Delete " , [ & ] {
2025-12-29 15:04:32 +03:00
selection . delete_all ( ) ;
2026-03-29 20:23:38 +03:00
} ) ;
popup_menu_action_button ( " Bring to front of layer " , " Bring to front of layer " , [ & ] {
2026-01-20 11:59:03 +03:00
selection . push_selection_to_front ( ) ;
2026-03-29 20:23:38 +03:00
} ) ;
popup_menu_action_button ( " Send to back of layer " , " Send to back of layer " , [ & ] {
2026-01-20 11:59:03 +03:00
selection . push_selection_to_back ( ) ;
2026-03-29 20:23:38 +03:00
} ) ;
2025-12-29 15:04:32 +03:00
}
2025-12-01 17:33:14 +03:00
} ) ;
}
2026-03-29 20:23:38 +03:00
void DrawingProgram : : right_click_popup_gui ( Vector2f popupPos ) {
2025-12-01 17:33:14 +03:00
Toolbar & t = world . main . toolbar ;
2026-03-31 13:17:58 +03:00
GUIStuff : : GUIManager & gui = world . main . g . gui ;
auto & io = gui . io ;
gui . new_id ( " Drawing Program right click GUI " , [ & ] {
2026-03-30 18:51:14 +03:00
drawTool - > right_click_popup_gui ( popupPos ) ;
} ) ;
2025-07-06 20:43:53 +03:00
}
void DrawingProgram : : tool_options_gui ( ) {
2026-03-29 20:23:38 +03:00
using namespace GUIStuff ;
2025-07-06 20:43:53 +03:00
Toolbar & t = world . main . toolbar ;
2026-03-31 13:17:58 +03:00
GUIStuff : : GUIManager & gui = world . main . g . gui ;
auto & io = gui . io ;
2025-08-02 20:39:21 +03:00
float minGUIWidth = drawTool - > get_type ( ) = = DrawingProgramToolType : : SCREENSHOT ? 300 : 200 ;
2026-03-31 17:33:09 +03:00
gui . element < LayoutElement > ( " Drawing program tool options gui " , [ & ] ( const Clay_ElementId & lId ) {
CLAY ( lId , {
2026-03-29 20:23:38 +03:00
. layout = {
. sizing = { . width = CLAY_SIZING_FIT ( minGUIWidth ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
2026-03-31 13:17:58 +03:00
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
. childGap = io . theme - > childGap1 ,
2026-03-29 20:23:38 +03:00
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
2026-03-31 13:17:58 +03:00
. backgroundColor = convert_vec4 < Clay_Color > ( io . theme - > backColor1 ) ,
. cornerRadius = CLAY_CORNER_RADIUS ( io . theme - > windowCorners1 )
2026-03-29 20:23:38 +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 ( ) {
2025-12-16 12:54:12 +03:00
if ( addFileInNextFrame ) {
add_file_to_canvas_by_path_execute ( addFileInfo . first , addFileInfo . second ) ;
addFileInNextFrame = false ;
}
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-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-03-03 16:01:21 +03:00
void DrawingProgram : : pen_tool_switch_check ( ) {
2026-01-07 10:34:51 +03:00
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 ;
2026-03-31 13:17:58 +03:00
world . main . g . gui . set_to_layout ( ) ;
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
}
2026-03-20 20:53:59 +03:00
SkPaint DrawingProgram : : select_tool_line_paint ( const DrawData & drawData ) {
2025-08-02 12:31:16 +03:00
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 ) ;
2026-03-20 16:24:52 +03:00
selectLinePaint . setBlender ( CanvasTheme : : get_visible_blend_mode ( ) ) ;
2026-03-20 20:53:59 +03:00
selectLinePaint . setAntiAlias ( drawData . skiaAA ) ;
2025-08-02 12:31:16 +03:00
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 : : 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 ( ) {
2026-03-31 13:33:15 +03:00
return 8.0f * world . main . g . final_gui_scale ( ) ;
2025-11-23 15:02:02 +03:00
}
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 ) ;
2026-03-20 20:53:59 +03:00
paintOutline . setAntiAlias ( drawData . skiaAA ) ;
2025-07-06 20:43:53 +03:00
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
}