2025-08-02 20:10:33 +03:00
# include "TextBoxEditTool.hpp"
2025-12-26 10:40:58 +03:00
# include "../../DrawingProgram.hpp"
# include "../../../MainProgram.hpp"
# include "../../../DrawData.hpp"
2025-08-02 20:10:33 +03:00
# include "Helpers/SCollision.hpp"
# include <cereal/types/vector.hpp>
2025-11-04 17:31:23 +03:00
# include <include/core/SkFontStyle.h>
2025-11-08 20:50:23 +03:00
# include <modules/skparagraph/include/DartTypes.h>
# include <modules/skparagraph/include/ParagraphStyle.h>
2025-11-09 16:24:40 +03:00
# include <modules/skparagraph/include/TextStyle.h>
2025-11-08 20:50:23 +03:00
# include <modules/skparagraph/src/ParagraphBuilderImpl.h>
# include <modules/skunicode/include/SkUnicode_icu.h>
2025-08-02 20:10:33 +03:00
# include <memory>
2025-12-26 10:40:58 +03:00
# include "../EditTool.hpp"
2025-08-02 20:10:33 +03:00
2026-03-30 17:05:24 +03:00
# include "../../../GUIStuff/ElementHelpers/TextLabelHelpers.hpp"
# include "../../../GUIStuff/ElementHelpers/LayoutHelpers.hpp"
2026-04-04 17:52:43 +03:00
# include "../../../GUIStuff/ElementHelpers/ButtonHelpers.hpp"
# include "../../../GUIStuff/ElementHelpers/NumberSliderHelpers.hpp"
2026-03-30 17:05:24 +03:00
# include "../../../GUIStuff/Elements/PositionAdjustingPopupMenu.hpp"
2026-04-05 11:02:48 +03:00
# include "../../../GUIStuff/Elements/FontPicker.hpp"
2026-03-30 17:05:24 +03:00
2025-11-10 14:45:51 +03:00
using namespace RichText ;
2026-04-04 17:52:43 +03:00
TextBoxEditTool : : TextBoxEditTool ( DrawingProgram & initDrawP , CanvasComponentContainer : : ObjInfo * initComp ) :
DrawingProgramEditToolBase ( initDrawP , initComp )
2025-08-02 20:10:33 +03:00
{ }
2026-04-04 17:52:43 +03:00
void TextBoxEditTool : : commit_update_func ( ) {
comp - > obj - > commit_update ( drawP ) ;
}
void TextBoxEditTool : : commit_update_and_layout_func ( ) {
comp - > obj - > commit_update ( drawP ) ;
drawP . world . main . g . gui . set_to_layout ( ) ;
}
void TextBoxEditTool : : edit_gui ( ) {
2026-03-30 17:05:24 +03:00
using namespace GUIStuff ;
using namespace ElementHelpers ;
2025-12-15 15:36:42 +03:00
auto & a = static_cast < TextBoxCanvasComponent & > ( comp - > obj - > get_comp ( ) ) ;
2025-08-02 20:10:33 +03:00
Toolbar & t = drawP . world . main . toolbar ;
2026-03-31 13:17:58 +03:00
auto & gui = drawP . world . main . g . gui ;
2026-03-07 13:12:11 +03:00
auto & currentMods = * currentModsPtr ;
2026-03-31 13:17:58 +03:00
gui . new_id ( " edit tool text " , [ & ] {
text_label_centered ( gui , " Edit Text " ) ;
2026-03-30 18:51:14 +03:00
2026-04-05 11:02:48 +03:00
left_to_right_line_layout ( gui , [ & ] {
text_label ( gui , " Font " ) ;
gui . element < FontPicker > ( " Font picker " , & newFontName , FontPickerData {
. onFontChange = [ & ] {
currentMods [ TextStyleModifier : : ModifierType : : FONT_FAMILIES ] = std : : make_shared < FontFamiliesTextStyleModifier > ( std : : vector < SkString > { SkString { newFontName . c_str ( ) , newFontName . size ( ) } } ) ;
add_undo_if_selecting_area ( a , [ & ] ( ) { a . textBox - > set_text_style_modifier_between ( a . cursor - > selectionBeginPos , a . cursor - > selectionEndPos , currentMods [ TextStyleModifier : : ModifierType : : FONT_FAMILIES ] ) ; } ) ;
commit_update_func ( ) ;
}
} ) ;
} ) ;
2026-04-04 17:52:43 +03:00
left_to_right_line_centered_layout ( gui , [ & ] {
svg_icon_button ( gui , " Bold button " , " data/icons/RemixIcon/bold.svg " , {
. isSelected = newIsBold ,
. onClick = [ & ] {
newIsBold = ! newIsBold ;
currentMods [ TextStyleModifier : : ModifierType : : WEIGHT ] = std : : make_shared < WeightTextStyleModifier > ( newIsBold ? SkFontStyle : : Weight : : kBold_Weight : SkFontStyle : : Weight : : kNormal_Weight ) ;
add_undo_if_selecting_area ( a , [ & ] ( ) { a . textBox - > set_text_style_modifier_between ( a . cursor - > selectionBeginPos , a . cursor - > selectionEndPos , currentMods [ TextStyleModifier : : ModifierType : : WEIGHT ] ) ; } ) ;
commit_update_func ( ) ;
}
} ) ;
svg_icon_button ( gui , " Italic button " , " data/icons/RemixIcon/italic.svg " , {
. isSelected = newIsItalic ,
. onClick = [ & ] {
newIsItalic = ! newIsItalic ;
currentMods [ TextStyleModifier : : ModifierType : : SLANT ] = std : : make_shared < SlantTextStyleModifier > ( newIsItalic ? SkFontStyle : : Slant : : kItalic_Slant : SkFontStyle : : Slant : : kUpright_Slant ) ;
add_undo_if_selecting_area ( a , [ & ] ( ) { a . textBox - > set_text_style_modifier_between ( a . cursor - > selectionBeginPos , a . cursor - > selectionEndPos , currentMods [ TextStyleModifier : : ModifierType : : SLANT ] ) ; } ) ;
commit_update_func ( ) ;
}
} ) ;
auto decorationChange = [ & ] {
currentMods [ TextStyleModifier : : ModifierType : : DECORATION ] = std : : make_shared < DecorationTextStyleModifier > ( get_new_decoration_value ( ) ) ;
add_undo_if_selecting_area ( a , [ & ] ( ) { a . textBox - > set_text_style_modifier_between ( a . cursor - > selectionBeginPos , a . cursor - > selectionEndPos , currentMods [ TextStyleModifier : : ModifierType : : DECORATION ] ) ; } ) ;
commit_update_func ( ) ;
} ;
svg_icon_button ( gui , " Underline button " , " data/icons/RemixIcon/underline.svg " , {
. isSelected = newIsUnderlined ,
. onClick = [ & , decorationChange ] {
newIsUnderlined = ! newIsUnderlined ;
decorationChange ( ) ;
}
} ) ;
svg_icon_button ( gui , " Strikethrough button " , " data/icons/RemixIcon/strikethrough.svg " , {
. isSelected = newIsLinethrough ,
. onClick = [ & , decorationChange ] {
newIsLinethrough = ! newIsLinethrough ;
decorationChange ( ) ;
}
} ) ;
svg_icon_button ( gui , " Overline button " , " data/icons/RemixIcon/overline.svg " , {
. isSelected = newIsOverline ,
. onClick = [ & , decorationChange ] {
newIsOverline = ! newIsOverline ;
decorationChange ( ) ;
}
} ) ;
} ) ;
2026-03-30 18:51:14 +03:00
2026-04-04 17:52:43 +03:00
left_to_right_line_centered_layout ( gui , [ & ] ( ) {
auto paragraph_operation_button = [ & ] ( const char * id , const char * svgPath , bool isSelected , const std : : function < void ( ) > & func ) {
svg_icon_button ( gui , id , svgPath , {
. isSelected = isSelected ,
. onClick = [ & , func ] {
add_undo ( func ) ;
commit_update_func ( ) ;
}
} ) ;
} ;
paragraph_operation_button ( " Align left button " , " data/icons/RemixIcon/align-left.svg " , currentPStyle . textAlignment = = skia : : textlayout : : TextAlign : : kLeft , [ & ] {
2026-04-05 11:02:48 +03:00
currentPStyle . textAlignment = skia : : textlayout : : TextAlign : : kLeft ;
2026-04-04 17:52:43 +03:00
a . textBox - > set_text_alignment_between ( a . cursor - > selectionBeginPos . fParagraphIndex , a . cursor - > selectionEndPos . fParagraphIndex , skia : : textlayout : : TextAlign : : kLeft ) ;
} ) ;
paragraph_operation_button ( " Align center button " , " data/icons/RemixIcon/align-center.svg " , currentPStyle . textAlignment = = skia : : textlayout : : TextAlign : : kCenter , [ & ] {
2026-04-05 11:02:48 +03:00
currentPStyle . textAlignment = skia : : textlayout : : TextAlign : : kCenter ;
2026-04-04 17:52:43 +03:00
a . textBox - > set_text_alignment_between ( a . cursor - > selectionBeginPos . fParagraphIndex , a . cursor - > selectionEndPos . fParagraphIndex , skia : : textlayout : : TextAlign : : kCenter ) ;
} ) ;
paragraph_operation_button ( " Align right button " , " data/icons/RemixIcon/align-right.svg " , currentPStyle . textAlignment = = skia : : textlayout : : TextAlign : : kRight , [ & ] {
2026-04-05 11:02:48 +03:00
currentPStyle . textAlignment = skia : : textlayout : : TextAlign : : kRight ;
2026-04-04 17:52:43 +03:00
a . textBox - > set_text_alignment_between ( a . cursor - > selectionBeginPos . fParagraphIndex , a . cursor - > selectionEndPos . fParagraphIndex , skia : : textlayout : : TextAlign : : kRight ) ;
} ) ;
paragraph_operation_button ( " Align justify button " , " data/icons/RemixIcon/align-justify.svg " , currentPStyle . textAlignment = = skia : : textlayout : : TextAlign : : kJustify , [ & ] {
2026-04-05 11:02:48 +03:00
currentPStyle . textAlignment = skia : : textlayout : : TextAlign : : kJustify ;
2026-04-04 17:52:43 +03:00
a . textBox - > set_text_alignment_between ( a . cursor - > selectionBeginPos . fParagraphIndex , a . cursor - > selectionEndPos . fParagraphIndex , skia : : textlayout : : TextAlign : : kJustify ) ;
} ) ;
paragraph_operation_button ( " Text direction left button " , " data/icons/RemixIcon/text-direction-l.svg " , currentPStyle . textDirection = = skia : : textlayout : : TextDirection : : kLtr , [ & ] {
2026-04-05 11:02:48 +03:00
currentPStyle . textDirection = skia : : textlayout : : TextDirection : : kLtr ;
2026-04-04 17:52:43 +03:00
a . textBox - > set_text_direction_between ( a . cursor - > selectionBeginPos . fParagraphIndex , a . cursor - > selectionEndPos . fParagraphIndex , skia : : textlayout : : TextDirection : : kLtr ) ;
} ) ;
paragraph_operation_button ( " Text direction right button " , " data/icons/RemixIcon/text-direction-r.svg " , currentPStyle . textDirection = = skia : : textlayout : : TextDirection : : kRtl , [ & ] {
2026-04-05 11:02:48 +03:00
currentPStyle . textDirection = skia : : textlayout : : TextDirection : : kRtl ;
2026-04-04 17:52:43 +03:00
a . textBox - > set_text_direction_between ( a . cursor - > selectionBeginPos . fParagraphIndex , a . cursor - > selectionEndPos . fParagraphIndex , skia : : textlayout : : TextDirection : : kRtl ) ;
} ) ;
} ) ;
2026-03-30 18:51:14 +03:00
2026-04-04 17:52:43 +03:00
slider_scalar_field < uint32_t > ( gui , " Font Size Slider " , " Font Size " , & newFontSize , 3 , 100 , TextBoxScalarOptions {
. onEdit = [ & ] {
currentMods [ TextStyleModifier : : ModifierType : : SIZE ] = std : : make_shared < SizeTextStyleModifier > ( newFontSize ) ;
a . textBox - > set_text_style_modifier_between ( a . cursor - > selectionBeginPos , a . cursor - > selectionEndPos , currentMods [ TextStyleModifier : : ModifierType : : SIZE ] ) ;
commit_update_func ( ) ;
} ,
. onSelect = [ & ] { hold_undo_data ( " Font Size " , a ) ; } ,
. onDeselect = [ & ] { release_undo_data ( " Font Size " ) ; }
} ) ;
2026-03-30 18:51:14 +03:00
2026-04-04 19:53:45 +03:00
left_to_right_line_layout ( gui , [ & ] ( ) {
t . color_button_right ( " Text Color " , & newTextColor , {
. onChange = [ & ] {
currentMods [ TextStyleModifier : : ModifierType : : COLOR ] = std : : make_shared < ColorTextStyleModifier > ( newTextColor ) ;
a . textBox - > set_text_style_modifier_between ( a . cursor - > selectionBeginPos , a . cursor - > selectionEndPos , currentMods [ TextStyleModifier : : ModifierType : : COLOR ] ) ;
commit_update_func ( ) ;
} ,
. onSelect = [ & ] { hold_undo_data ( " Text Color " , a ) ; } ,
. onDeselect = [ & ] { release_undo_data ( " Text Color " ) ; }
} ) ;
text_label ( gui , " Text Color " ) ;
} ) ;
2026-03-30 18:51:14 +03:00
2026-04-04 19:53:45 +03:00
left_to_right_line_layout ( gui , [ & ] ( ) {
t . color_button_right ( " Highlight Color " , & newHighlightColor , {
. onSelectorButtonClick = [ & ] {
if ( newHighlightColor . w ( ) = = 0.0f ) {
newHighlightColor . w ( ) = 1.0f ;
currentMods [ TextStyleModifier : : ModifierType : : HIGHLIGHT_COLOR ] = std : : make_shared < HighlightColorTextStyleModifier > ( newHighlightColor ) ;
add_undo_if_selecting_area ( a , [ & ] ( ) { a . textBox - > set_text_style_modifier_between ( a . cursor - > selectionBeginPos , a . cursor - > selectionEndPos , currentMods [ TextStyleModifier : : ModifierType : : HIGHLIGHT_COLOR ] ) ; } ) ;
commit_update_and_layout_func ( ) ;
}
} ,
. onChange = [ & ] {
currentMods [ TextStyleModifier : : ModifierType : : HIGHLIGHT_COLOR ] = std : : make_shared < HighlightColorTextStyleModifier > ( newHighlightColor ) ;
a . textBox - > set_text_style_modifier_between ( a . cursor - > selectionBeginPos , a . cursor - > selectionEndPos , currentMods [ TextStyleModifier : : ModifierType : : HIGHLIGHT_COLOR ] ) ;
commit_update_and_layout_func ( ) ;
} ,
. onSelect = [ & ] { hold_undo_data ( " Highlight Color " , a ) ; } ,
. onDeselect = [ & ] { release_undo_data ( " Highlight Color " ) ; }
} ) ;
if ( newHighlightColor . w ( ) ! = 0.0f ) {
svg_icon_button ( gui , " Remove Highlight Color " , " data/icons/close.svg " , {
. onClick = [ & ] {
newHighlightColor = { 0.0f , 0.0f , 0.0f , 0.0f } ;
currentMods [ TextStyleModifier : : ModifierType : : HIGHLIGHT_COLOR ] = std : : make_shared < HighlightColorTextStyleModifier > ( newHighlightColor ) ;
add_undo_if_selecting_area ( a , [ & ] ( ) { a . textBox - > set_text_style_modifier_between ( a . cursor - > selectionBeginPos , a . cursor - > selectionEndPos , currentMods [ TextStyleModifier : : ModifierType : : HIGHLIGHT_COLOR ] ) ; } ) ;
commit_update_and_layout_func ( ) ;
}
} ) ;
}
text_label ( gui , " Highlight Color " ) ;
} ) ;
2025-11-08 20:50:23 +03:00
} ) ;
2025-08-02 20:10:33 +03:00
}
2026-04-11 16:36:24 +03:00
void TextBoxEditTool : : input_paste_callback ( const CustomEvents : : PasteEvent & paste ) {
2026-04-04 17:52:43 +03:00
if ( textboxID . has_value ( ) & & textboxID . value ( ) = = drawP . world . main . input . text . current_textbox_editing_id ( ) ) {
auto & a = static_cast < TextBoxCanvasComponent & > ( comp - > obj - > get_comp ( ) ) ;
if ( userInput - > input_paste_callback ( paste ) ) {
commit_update_and_layout_func ( ) ;
set_styles_at_selection ( a ) ;
}
}
}
void TextBoxEditTool : : input_text_key_callback ( const InputManager : : KeyCallbackArgs & key ) {
if ( textboxID . has_value ( ) & & textboxID . value ( ) = = drawP . world . main . input . text . current_textbox_editing_id ( ) ) {
auto & a = static_cast < TextBoxCanvasComponent & > ( comp - > obj - > get_comp ( ) ) ;
auto changes = userInput - > input_key_callback ( drawP . world . main . input , key ) ;
if ( changes . textEdited | | changes . cursorChanged ) {
commit_update_and_layout_func ( ) ;
set_styles_at_selection ( a ) ;
}
}
}
void TextBoxEditTool : : input_text_callback ( const InputManager : : TextCallbackArgs & text ) {
if ( textboxID . has_value ( ) & & textboxID . value ( ) = = drawP . world . main . input . text . current_textbox_editing_id ( ) ) {
auto & a = static_cast < TextBoxCanvasComponent & > ( comp - > obj - > get_comp ( ) ) ;
userInput - > add_text_to_textbox ( text . str ) ;
commit_update_and_layout_func ( ) ;
set_styles_at_selection ( a ) ;
}
2026-03-07 13:00:10 +03:00
}
2026-04-04 17:52:43 +03:00
void TextBoxEditTool : : input_mouse_button_on_canvas_callback ( const InputManager : : MouseButtonCallbackArgs & button , bool isDraggingPoint ) {
2026-03-07 13:00:10 +03:00
if ( button . button = = InputManager : : MouseButton : : LEFT & & ! isDraggingPoint ) {
auto & a = static_cast < TextBoxCanvasComponent & > ( comp - > obj - > get_comp ( ) ) ;
SCollision : : ColliderCollection < float > mousePointCollection ;
mousePointCollection . circle . emplace_back ( button . pos , 1.0f ) ;
mousePointCollection . recalculate_bounds ( ) ;
InputManager & input = drawP . world . main . input ;
bool collidesWithBox = comp - > obj - > collides_with_cam_coords ( drawP . world . drawData . cam . c , mousePointCollection ) ;
2026-04-04 17:52:43 +03:00
auto oldCursor = * a . cursor ;
2026-03-07 13:00:10 +03:00
a . textBox - > process_mouse_left_button ( * a . cursor , a . get_mouse_pos ( drawP ) , ( button . down & & button . clicks & & collidesWithBox ) ? button . clicks : 0 , button . down , input . key ( InputManager : : KEY_GENERIC_LSHIFT ) . held ) ;
2026-04-04 17:52:43 +03:00
if ( oldCursor ! = * a . cursor ) {
set_styles_at_selection ( a ) ;
commit_update_and_layout_func ( ) ;
}
2026-03-07 13:00:10 +03:00
}
}
2026-04-04 17:52:43 +03:00
void TextBoxEditTool : : input_mouse_motion_callback ( const InputManager : : MouseMotionCallbackArgs & motion , bool isDraggingPoint ) {
2026-03-07 13:00:10 +03:00
auto & a = static_cast < TextBoxCanvasComponent & > ( comp - > obj - > get_comp ( ) ) ;
a . init_text_box ( drawP ) ;
if ( ! isDraggingPoint ) {
auto & a = static_cast < TextBoxCanvasComponent & > ( comp - > obj - > get_comp ( ) ) ;
SCollision : : ColliderCollection < float > mousePointCollection ;
mousePointCollection . circle . emplace_back ( motion . pos , 1.0f ) ;
mousePointCollection . recalculate_bounds ( ) ;
InputManager & input = drawP . world . main . input ;
2026-04-04 17:52:43 +03:00
auto oldCursor = * a . cursor ;
2026-03-07 13:00:10 +03:00
a . textBox - > process_mouse_left_button ( * a . cursor , a . get_mouse_pos ( drawP ) , 0 , drawP . controls . leftClickHeld , input . key ( InputManager : : KEY_GENERIC_LSHIFT ) . held ) ;
2026-04-04 17:52:43 +03:00
if ( oldCursor ! = * a . cursor ) {
set_styles_at_selection ( a ) ;
commit_update_and_layout_func ( ) ;
}
2026-03-07 13:00:10 +03:00
}
}
2026-04-04 17:52:43 +03:00
void TextBoxEditTool : : right_click_popup_gui ( Vector2f popupPos ) {
2026-03-30 17:05:24 +03:00
using namespace GUIStuff ;
using namespace ElementHelpers ;
2026-03-31 13:17:58 +03:00
auto & gui = drawP . world . main . g . gui ;
2025-12-15 15:36:42 +03:00
auto & a = static_cast < TextBoxCanvasComponent & > ( comp - > obj - > get_comp ( ) ) ;
2026-03-30 17:05:24 +03:00
2026-04-04 21:31:18 +03:00
gui . set_z_index ( - 1 , [ & ] {
gui . element < PositionAdjustingPopupMenu > ( " Text popup menu " , popupPos , [ & ] {
text_label_light ( gui , " Text menu " ) ;
InputManager & input = drawP . world . main . input ;
drawP . popup_menu_action_button ( " Paste " , " Paste " , [ & ] {
2026-04-11 16:36:24 +03:00
drawP . world . main . input . call_paste ( CustomEvents : : PasteEvent : : DataType : : TEXT , { . allowRichText = true } ) ;
2026-04-04 21:31:18 +03:00
} ) ;
drawP . popup_menu_action_button ( " Paste without formatting " , " Paste without formatting " , [ & ] {
2026-04-11 16:36:24 +03:00
drawP . world . main . input . call_paste ( CustomEvents : : PasteEvent : : DataType : : TEXT , { . allowRichText = false } ) ;
2026-04-04 21:31:18 +03:00
} ) ;
if ( a . cursor - > selectionBeginPos ! = a . cursor - > selectionEndPos ) {
drawP . popup_menu_action_button ( " Copy " , " Copy " , [ & ] {
input . set_clipboard_plain_and_richtext_pair ( a . textBox - > process_copy ( * a . cursor ) ) ;
} ) ;
drawP . popup_menu_action_button ( " Cut " , " Cut " , [ & ] {
userInput - > do_textbox_operation_with_undo ( [ & ] ( ) {
input . set_clipboard_plain_and_richtext_pair ( a . textBox - > process_cut ( * a . cursor ) ) ;
} ) ;
commit_update_and_layout_func ( ) ;
set_styles_at_selection ( a ) ;
} ) ;
}
} , LayoutElement : : Callbacks {
. mouseButton = [ & ] ( LayoutElement * , const InputManager : : MouseButtonCallbackArgs & button ) {
if ( button . down & & button . button ! = InputManager : : MouseButton : : RIGHT )
drawP . clear_right_click_popup ( ) ;
}
} ) ;
2025-12-02 09:53:03 +03:00
} ) ;
}
2025-12-15 15:36:42 +03:00
void TextBoxEditTool : : hold_undo_data ( const std : : string & undoName , TextBoxCanvasComponent & a ) {
2025-11-12 14:54:46 +03:00
auto it = undoHeldData . find ( undoName ) ;
if ( it ! = undoHeldData . end ( ) ) {
std : : pair < RichText : : TextBox : : Cursor , RichText : : TextData > & undoData = it - > second ;
2025-12-15 15:36:42 +03:00
if ( undoData . first ! = * a . cursor )
2025-11-12 14:54:46 +03:00
undoHeldData . erase ( it ) ;
}
2025-12-15 15:36:42 +03:00
else if ( a . cursor - > selectionBeginPos ! = a . cursor - > selectionEndPos )
undoHeldData . emplace ( undoName , std : : pair < RichText : : TextBox : : Cursor , RichText : : TextData > { * a . cursor , a . textBox - > get_rich_text_data ( ) } ) ;
2025-11-12 14:54:46 +03:00
}
void TextBoxEditTool : : release_undo_data ( const std : : string & undoName ) {
auto it = undoHeldData . find ( undoName ) ;
if ( it ! = undoHeldData . end ( ) ) {
std : : pair < RichText : : TextBox : : Cursor , RichText : : TextData > & undoData = it - > second ;
2026-04-04 17:52:43 +03:00
userInput - > add_textbox_undo ( undoData . first , undoData . second ) ;
2025-11-12 14:54:46 +03:00
undoHeldData . erase ( it ) ;
}
}
2025-12-15 15:36:42 +03:00
void TextBoxEditTool : : add_undo_if_selecting_area ( TextBoxCanvasComponent & a , const std : : function < void ( ) > & func ) {
if ( a . cursor - > selectionBeginPos = = a . cursor - > selectionEndPos )
2025-11-12 14:54:46 +03:00
func ( ) ;
2026-04-04 17:52:43 +03:00
else
userInput - > do_textbox_operation_with_undo ( func ) ;
2025-11-12 14:54:46 +03:00
}
void TextBoxEditTool : : add_undo ( const std : : function < void ( ) > & func ) {
2026-04-04 17:52:43 +03:00
userInput - > do_textbox_operation_with_undo ( func ) ;
2025-11-12 14:54:46 +03:00
}
2025-11-09 16:24:40 +03:00
uint8_t TextBoxEditTool : : get_new_decoration_value ( ) {
uint8_t toRet = skia : : textlayout : : TextDecoration : : kNoDecoration ;
if ( newIsUnderlined )
toRet | = skia : : textlayout : : TextDecoration : : kUnderline ;
if ( newIsLinethrough )
toRet | = skia : : textlayout : : TextDecoration : : kLineThrough ;
if ( newIsOverline )
toRet | = skia : : textlayout : : TextDecoration : : kOverline ;
return toRet ;
}
2025-12-15 15:36:42 +03:00
void TextBoxEditTool : : set_styles_at_selection ( TextBoxCanvasComponent & a ) {
2026-03-07 13:12:11 +03:00
auto & currentMods = * currentModsPtr ;
2025-12-15 15:36:42 +03:00
TextPosition start = std : : min ( a . cursor - > selectionBeginPos , a . cursor - > selectionEndPos ) ;
TextPosition end = std : : max ( a . cursor - > selectionBeginPos , a . cursor - > selectionEndPos ) ;
2025-11-05 14:55:22 +03:00
if ( start = = end )
2025-12-15 15:36:42 +03:00
currentMods = a . textBox - > get_mods_used_at_pos ( a . textBox - > move ( TextBox : : Movement : : LEFT , start ) ) ;
2025-11-05 14:55:22 +03:00
else
2025-12-15 15:36:42 +03:00
currentMods = a . textBox - > get_mods_used_at_pos ( start ) ;
2025-11-10 16:49:02 +03:00
newFontName = std : : static_pointer_cast < FontFamiliesTextStyleModifier > ( currentMods [ TextStyleModifier : : ModifierType : : FONT_FAMILIES ] ) - > get_families ( ) . back ( ) . c_str ( ) ;
newFontSize = std : : static_pointer_cast < SizeTextStyleModifier > ( currentMods [ TextStyleModifier : : ModifierType : : SIZE ] ) - > get_size ( ) ;
newTextColor = std : : static_pointer_cast < ColorTextStyleModifier > ( currentMods [ TextStyleModifier : : ModifierType : : COLOR ] ) - > get_color ( ) ;
2025-11-24 17:45:21 +03:00
newHighlightColor = std : : static_pointer_cast < HighlightColorTextStyleModifier > ( currentMods [ TextStyleModifier : : ModifierType : : HIGHLIGHT_COLOR ] ) - > get_color ( ) ;
2025-11-10 16:49:02 +03:00
newIsBold = std : : static_pointer_cast < WeightTextStyleModifier > ( currentMods [ TextStyleModifier : : ModifierType : : WEIGHT ] ) - > get_weight ( ) = = SkFontStyle : : Weight : : kBold_Weight ;
newIsItalic = std : : static_pointer_cast < SlantTextStyleModifier > ( currentMods [ TextStyleModifier : : ModifierType : : SLANT ] ) - > get_slant ( ) = = SkFontStyle : : Slant : : kItalic_Slant ;
newIsUnderlined = std : : static_pointer_cast < DecorationTextStyleModifier > ( currentMods [ TextStyleModifier : : ModifierType : : DECORATION ] ) - > get_decoration_value ( ) & skia : : textlayout : : TextDecoration : : kUnderline ;
newIsLinethrough = std : : static_pointer_cast < DecorationTextStyleModifier > ( currentMods [ TextStyleModifier : : ModifierType : : DECORATION ] ) - > get_decoration_value ( ) & skia : : textlayout : : TextDecoration : : kLineThrough ;
newIsOverline = std : : static_pointer_cast < DecorationTextStyleModifier > ( currentMods [ TextStyleModifier : : ModifierType : : DECORATION ] ) - > get_decoration_value ( ) & skia : : textlayout : : TextDecoration : : kOverline ;
2025-12-15 15:36:42 +03:00
currentPStyle = a . textBox - > get_paragraph_style_data_at ( start . fParagraphIndex ) ;
2025-11-05 14:55:22 +03:00
}
2026-04-04 17:52:43 +03:00
void TextBoxEditTool : : commit_edit_updates ( std : : any & prevData ) {
2025-12-15 15:36:42 +03:00
auto & a = static_cast < TextBoxCanvasComponent & > ( comp - > obj - > get_comp ( ) ) ;
a . d . editing = false ;
comp - > obj - > commit_update ( drawP ) ;
2026-04-04 17:52:43 +03:00
if ( textboxID . has_value ( ) )
drawP . world . main . input . remove_text_box ( textboxID . value ( ) ) ;
2025-08-02 20:10:33 +03:00
}
2025-12-15 15:36:42 +03:00
TextBoxEditTool : : TextBoxEditToolAllData TextBoxEditTool : : get_all_data ( const TextBoxCanvasComponent & a ) {
2025-10-25 17:31:55 +03:00
return {
2025-12-15 15:36:42 +03:00
. textboxData = a . d ,
. richText = a . textBox - > get_rich_text_data ( )
2025-10-25 17:31:55 +03:00
} ;
}
2026-04-04 17:52:43 +03:00
void TextBoxEditTool : : edit_start ( EditTool & editTool , std : : any & prevData ) {
2025-12-15 15:36:42 +03:00
auto & a = static_cast < TextBoxCanvasComponent & > ( comp - > obj - > get_comp ( ) ) ;
auto & cur = a . cursor ;
auto & textbox = a . textBox ;
2026-03-07 13:00:10 +03:00
a . init_text_box ( drawP ) ;
2025-11-10 14:45:51 +03:00
cur = std : : make_shared < TextBox : : Cursor > ( ) ;
2025-12-15 15:36:42 +03:00
Vector2f textSelectPos = a . get_mouse_pos ( drawP ) ;
2025-11-27 16:12:32 +03:00
textbox - > process_mouse_left_button ( * cur , textSelectPos , 1 , false , false ) ;
2025-11-04 10:58:26 +03:00
prevData = get_all_data ( a ) ;
2025-12-15 15:36:42 +03:00
a . d . editing = true ;
2025-11-05 12:29:31 +03:00
2025-11-10 16:49:02 +03:00
set_styles_at_selection ( a ) ;
2025-11-05 12:29:31 +03:00
2025-12-15 15:36:42 +03:00
editTool . add_point_handle ( { & a . d . p1 , nullptr , & a . d . p2 } ) ;
editTool . add_point_handle ( { & a . d . p2 , & a . d . p1 , nullptr } ) ;
2026-01-06 12:36:15 +03:00
comp - > obj - > commit_update ( drawP ) ;
2026-03-07 13:00:10 +03:00
2026-04-04 17:52:43 +03:00
userInput = std : : make_unique < RichTextUserInput > ( textbox , cur , currentModsPtr ) ;
textboxID = drawP . world . main . input . set_text_box_back ( std : : nullopt , {
. inputType = SDL_TextInputType : : SDL_TEXTINPUT_TYPE_TEXT ,
. capitalization = SDL_Capitalization : : SDL_CAPITALIZE_NONE ,
. autocorrect = true ,
. multiline = true ,
. androidInputType = InputManager : : AndroidInputType : : ANDROIDTEXT_TYPE_CLASS_TEXT
} ) ;
2025-08-02 20:10:33 +03:00
}
2026-04-04 17:52:43 +03:00
bool TextBoxEditTool : : edit_update ( ) {
2025-08-02 20:10:33 +03:00
return true ;
}