2026-05-11 23:06:39 +03:00
/*
* InfiniPaint
* Copyright ( C ) 2025 - 2026 Yousef Khadadeh
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < https : //www.gnu.org/licenses/>.
*/
2025-07-06 20:43:53 +03:00
# include "Toolbar.hpp"
2026-04-05 20:26:03 +03:00
# include "CustomEvents.hpp"
2025-12-26 10:40:58 +03:00
# include "DrawingProgram/Tools/DrawingProgramToolBase.hpp"
2026-03-05 15:03:05 +03:00
# include "FileHelpers.hpp"
2026-03-27 09:51:53 +03:00
# include "GUIStuff/ElementHelpers/CheckBoxHelpers.hpp"
# include "GUIStuff/ElementHelpers/LayoutHelpers.hpp"
# include "GUIStuff/ElementHelpers/PopupHelpers.hpp"
# include "GUIStuff/ElementHelpers/RadioButtonHelpers.hpp"
2026-04-30 14:45:36 +03:00
# include "GUIStuff/Elements/ManyElementScrollArea.hpp"
2025-07-06 20:43:53 +03:00
# include "Helpers/ConvertVec.hpp"
2025-10-01 20:08:01 +03:00
# include "Helpers/FileDownloader.hpp"
2026-01-08 16:37:18 +03:00
# include "Helpers/MathExtras.hpp"
2025-07-06 20:43:53 +03:00
# include "Helpers/Networking/NetLibrary.hpp"
2025-12-10 13:06:45 +03:00
# include "Helpers/NetworkingObjects/NetObjGenericSerializedClass.hpp"
2025-07-06 20:43:53 +03:00
# include "MainProgram.hpp"
# include "InputManager.hpp"
2026-02-19 15:21:47 +03:00
# include "ResourceDisplay/ImageResourceDisplay.hpp"
2026-04-09 16:30:28 +03:00
# include "RichText/TextStyleModifier.hpp"
2025-11-16 16:39:12 +03:00
# include "VersionConstants.hpp"
2025-07-06 20:43:53 +03:00
# include "World.hpp"
# include <SDL3/SDL_dialog.h>
2026-02-24 17:21:41 +03:00
# include <SDL3/SDL_filesystem.h>
2025-11-20 12:38:28 +03:00
# include <SDL3/SDL_render.h>
2025-07-06 20:43:53 +03:00
# include <algorithm>
# include <filesystem>
# include <optional>
# include <Helpers/Logger.hpp>
# include <Helpers/StringHelpers.hpp>
2025-10-01 20:08:01 +03:00
# include <Helpers/VersionNumber.hpp>
2025-10-15 16:33:24 +03:00
# include <modules/skparagraph/src/ParagraphBuilderImpl.h>
# include <modules/skparagraph/include/ParagraphStyle.h>
# include <modules/skparagraph/include/FontCollection.h>
# include <modules/skparagraph/include/TextStyle.h>
2025-10-16 09:38:58 +03:00
# include <include/core/SkFontStyle.h>
2025-10-15 16:33:24 +03:00
# include <modules/skunicode/include/SkUnicode_icu.h>
2025-11-21 10:36:46 +03:00
# include <modules/svg/include/SkSVGNode.h>
# include <include/core/SkStream.h>
2026-03-27 09:51:53 +03:00
# include "GUIStuff/GUIManager.hpp"
# include "GUIStuff/ElementHelpers/TextLabelHelpers.hpp"
# include "GUIStuff/ElementHelpers/ButtonHelpers.hpp"
# include "GUIStuff/ElementHelpers/PopupHelpers.hpp"
# include "GUIStuff/ElementHelpers/TextBoxHelpers.hpp"
# include "GUIStuff/ElementHelpers/ColorPickerHelpers.hpp"
# include "GUIStuff/ElementHelpers/RadioButtonHelpers.hpp"
# include "GUIStuff/ElementHelpers/NumberSliderHelpers.hpp"
# include "GUIStuff/Elements/ScrollArea.hpp"
# include "GUIStuff/Elements/LayoutElement.hpp"
# include "GUIStuff/Elements/DropDown.hpp"
# include "GUIStuff/Elements/SVGIcon.hpp"
2026-04-05 20:26:03 +03:00
# include "GUIStuff/Elements/MovableTabList.hpp"
2026-04-09 16:30:28 +03:00
# include "GUIStuff/Elements/TextParagraph.hpp"
2026-03-27 09:51:53 +03:00
2026-05-12 21:13:31 +03:00
# include "Screens/DesktopDrawingProgramScreen.hpp"
2025-07-06 20:43:53 +03:00
# ifdef __EMSCRIPTEN__
# include <EmscriptenHelpers/emscripten_browser_file.h>
# endif
2026-03-27 09:51:53 +03:00
using namespace GUIStuff ;
using namespace ElementHelpers ;
2026-05-12 21:13:31 +03:00
Toolbar : : Toolbar ( MainProgram & initMain , DesktopDrawingProgramScreen & initDrawScreen ) :
main ( initMain ) ,
drawScreen ( initDrawScreen )
2026-04-12 12:36:00 +03:00
{ }
2025-07-06 20:43:53 +03:00
2026-05-12 21:13:31 +03:00
void Toolbar : : open_file_selector ( const std : : string & filePickerName , const std : : vector < Screen : : ExtensionFilter > & extensionFilters , Screen : : OpenFileSelectorCallback postSelectionFunc , const std : : string & fileName , bool isSaving ) {
drawScreen . open_file_selector ( filePickerName , extensionFilters , postSelectionFunc , fileName , isSaving ) ;
2025-07-06 20:43:53 +03:00
}
2026-05-12 21:13:31 +03:00
void Toolbar : : open_file_selector_non_native ( const std : : string & filePickerName , const std : : vector < Screen : : ExtensionFilter > & extensionFilters , Screen : : OpenFileSelectorCallback postSelectionFunc , const std : : string & fileName , bool isSaving ) {
filePicker . isOpen = true ;
filePicker . extensionFiltersComplete = extensionFilters ;
filePicker . extensionFilters . clear ( ) ;
for ( auto & [ name , exList ] : extensionFilters )
filePicker . extensionFilters . emplace_back ( exList ) ;
filePicker . extensionSelected = extensionFilters . size ( ) - 1 ;
filePicker . filePickerWindowName = filePickerName ;
filePicker . postSelectionFunc = postSelectionFunc ;
filePicker . fileName = " " ;
filePicker . isSaving = isSaving ;
filePicker . entriesScrollArea = nullptr ;
file_picker_gui_refresh_entries ( ) ;
2025-07-06 20:43:53 +03:00
}
2026-04-04 19:53:45 +03:00
void Toolbar : : color_button_left ( const char * id , Vector4f * color , const ColorSelectorButtonData & colorSelectorData ) {
2026-04-02 18:23:36 +03:00
auto & gui = main . g . gui ;
color_button ( gui , id , color , {
. isSelected = colorLeft = = color ,
2026-04-06 12:23:46 +03:00
. onClickButton = [ & , colorSelectorData , color ] ( SelectableButton * b ) {
2026-04-04 19:53:45 +03:00
if ( colorSelectorData . onSelectorButtonClick ) colorSelectorData . onSelectorButtonClick ( ) ;
2026-04-06 12:23:46 +03:00
color_selector_left ( b , color , {
2026-04-04 19:53:45 +03:00
. onChange = colorSelectorData . onChange ,
. onSelect = colorSelectorData . onSelect ,
. onDeselect = colorSelectorData . onDeselect ,
} ) ;
2026-04-02 18:23:36 +03:00
}
} ) ;
}
2026-04-04 19:53:45 +03:00
void Toolbar : : color_button_right ( const char * id , Vector4f * color , const ColorSelectorButtonData & colorSelectorData ) {
2026-04-02 18:23:36 +03:00
auto & gui = main . g . gui ;
color_button ( gui , id , color , {
. isSelected = colorRight = = color ,
2026-04-06 12:23:46 +03:00
. onClickButton = [ & , colorSelectorData , color ] ( SelectableButton * b ) {
2026-04-04 19:53:45 +03:00
if ( colorSelectorData . onSelectorButtonClick ) colorSelectorData . onSelectorButtonClick ( ) ;
2026-04-06 12:23:46 +03:00
color_selector_right ( b , color , {
2026-04-04 19:53:45 +03:00
. onChange = colorSelectorData . onChange ,
. onSelect = colorSelectorData . onSelect ,
. onDeselect = colorSelectorData . onDeselect ,
} ) ;
2026-04-02 18:23:36 +03:00
}
} ) ;
}
2026-04-06 12:23:46 +03:00
void Toolbar : : color_selector_left ( Element * button , Vector4f * color , const ColorSelectorData & colorSelectorData ) {
2026-04-02 18:23:36 +03:00
if ( colorLeft ! = color ) {
2026-04-06 12:23:46 +03:00
colorLeft = color ;
2026-04-04 19:53:45 +03:00
colorLeftData = colorSelectorData ;
2026-04-06 12:23:46 +03:00
colorLeftButton = button ;
2026-04-02 18:23:36 +03:00
}
else
2026-04-06 12:23:46 +03:00
colorLeft = nullptr ;
2026-04-02 18:23:36 +03:00
main . g . gui . set_to_layout ( ) ;
2025-07-06 20:43:53 +03:00
}
2026-04-06 12:23:46 +03:00
void Toolbar : : color_selector_right ( Element * button , Vector4f * color , const ColorSelectorData & colorSelectorData ) {
2026-04-02 18:23:36 +03:00
if ( colorRight ! = color ) {
2026-04-06 12:23:46 +03:00
colorRight = color ;
2026-04-04 19:53:45 +03:00
colorRightData = colorSelectorData ;
2026-04-06 12:23:46 +03:00
colorRightButton = button ;
2026-04-02 18:23:36 +03:00
}
else
2026-04-06 12:23:46 +03:00
colorRight = nullptr ;
2026-04-02 18:23:36 +03:00
main . g . gui . set_to_layout ( ) ;
2025-07-06 20:43:53 +03:00
}
2026-04-05 22:01:30 +03:00
void Toolbar : : update ( ) {
std : : erase_if ( main . logMessages , [ & ] ( auto & logM ) {
logM . time . update_time_since ( ) ;
2026-04-12 12:36:00 +03:00
if ( logM . time > UserLogMessage : : FADE_START_TIME ) {
2026-04-05 22:01:30 +03:00
main . g . gui . set_to_layout ( ) ;
2026-04-12 12:36:00 +03:00
return logM . time > = UserLogMessage : : DISPLAY_TIME ;
2026-04-05 22:01:30 +03:00
}
return false ;
} ) ;
2026-04-09 16:30:28 +03:00
if ( ! chatboxOpen ) {
for ( auto & chatMessage : main . world - > chatMessages ) {
bool wasShown = chatMessage . time < ChatMessage : : DISPLAY_TIME ;
chatMessage . time . update_time_since ( ) ;
bool isShown = chatMessage . time < ChatMessage : : DISPLAY_TIME ;
bool isFading = chatMessage . time > = ChatMessage : : FADE_START_TIME ;
if ( ( isFading & & isShown ) | | ( wasShown & & ! isShown ) )
main . g . gui . set_to_layout ( ) ;
}
}
}
void Toolbar : : open_chatbox ( ) {
if ( ! chatboxOpen ) {
chatMessageInput . clear ( ) ;
chatboxOpen = true ;
main . g . gui . set_to_layout ( ) ;
}
}
void Toolbar : : close_chatbox ( ) {
if ( chatboxOpen ) {
chatboxOpen = false ;
main . g . gui . set_to_layout ( ) ;
}
2026-04-05 22:01:30 +03:00
}
2026-05-18 13:38:16 +03:00
void Toolbar : : toggle_player_list ( ) {
playerMenuOpen = ! playerMenuOpen ;
main . g . gui . set_to_layout ( ) ;
}
2026-03-27 09:51:53 +03:00
void Toolbar : : layout_run ( ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
auto & io = gui . io ;
2026-04-12 12:36:00 +03:00
if ( drawGui ) {
2025-12-02 16:09:30 +03:00
CLAY_AUTO_ID ( {
2025-07-06 20:43:53 +03:00
. layout = {
2026-04-26 15:47:28 +03:00
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
2026-03-31 13:17:58 +03:00
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
. childGap = io . theme - > childGap1 ,
2025-07-06 20:43:53 +03:00
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
}
} ) {
top_toolbar ( ) ;
2026-01-09 21:56:58 +03:00
if ( ! main . world - > clientStillConnecting )
drawing_program_gui ( ) ;
2026-01-18 13:39:49 +03:00
if ( ! closePopupData . worldsToClose . empty ( ) )
close_popup_gui ( ) ;
2026-03-31 13:17:58 +03:00
if ( main . conf . viewWebVersionWelcome )
2025-07-06 20:43:53 +03:00
web_version_welcome ( ) ;
2026-05-17 17:32:05 +03:00
else if ( main . updateCheckerData . showGui )
2025-10-01 20:08:01 +03:00
update_notification_gui ( ) ;
2025-07-06 20:43:53 +03:00
else if ( filePicker . isOpen )
file_picker_gui ( ) ;
else if ( optionsMenuOpen )
options_menu ( ) ;
2026-01-09 21:56:58 +03:00
else if ( main . world - > clientStillConnecting )
2026-05-13 14:31:35 +03:00
center_message ( " Connecting to server message " , " Connecting to server... " ) ;
2025-07-06 20:43:53 +03:00
else if ( playerMenuOpen )
player_list ( ) ;
2026-01-09 21:56:58 +03:00
else if ( ! main . world - > drawProg . layerMan . is_a_layer_being_edited ( ) )
2026-05-13 14:31:35 +03:00
center_message ( " Select layer to edit message " , " Select a layer to edit " ) ;
2025-07-06 20:43:53 +03:00
}
2026-01-09 21:56:58 +03:00
if ( ! main . world - > clientStillConnecting )
chat_box ( ) ;
2025-07-06 20:43:53 +03:00
}
2026-01-23 09:40:50 +03:00
else {
if ( ! closePopupData . worldsToClose . empty ( ) ) // Should still show close popup if gui is disabled
close_popup_gui ( ) ;
}
2025-07-06 20:43:53 +03:00
2026-04-05 15:02:26 +03:00
if ( ! main . world - > clientStillConnecting )
2026-04-12 12:36:00 +03:00
main . world - > drawProg . right_click_popup_gui ( * this ) ;
2025-07-06 20:43:53 +03:00
}
2026-01-18 13:39:49 +03:00
bool Toolbar : : app_close_requested ( ) {
for ( auto & w : main . worlds ) {
if ( w - > should_ask_before_closing ( ) )
add_world_to_close_popup_data ( w ) ;
}
closePopupData . closeAppWhenDone = true ;
2026-04-07 20:33:48 +03:00
main . g . gui . set_to_layout ( ) ;
2026-01-18 13:39:49 +03:00
return closePopupData . worldsToClose . empty ( ) ;
}
void Toolbar : : add_world_to_close_popup_data ( const std : : shared_ptr < World > & w ) {
auto it = std : : find_if ( closePopupData . worldsToClose . begin ( ) , closePopupData . worldsToClose . end ( ) , [ & ] ( auto & wStruct ) {
return wStruct . w . lock ( ) = = w ;
} ) ;
if ( it = = closePopupData . worldsToClose . end ( ) )
closePopupData . worldsToClose . emplace_back ( w , true ) ;
2026-04-05 20:26:03 +03:00
main . g . gui . set_to_layout ( ) ;
2026-01-18 13:39:49 +03:00
}
void Toolbar : : close_popup_gui ( ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
auto & io = gui . io ;
2026-01-18 13:39:49 +03:00
std : : erase_if ( closePopupData . worldsToClose , [ ] ( auto & wPair ) {
return wPair . w . expired ( ) ;
} ) ;
2026-03-31 18:41:19 +03:00
center_obstructing_window_gui ( " Close program popup GUI " , CLAY_SIZING_FIT ( 0 ) , CLAY_SIZING_FIT ( 0 , 600 ) , [ & ] {
text_label ( gui , " Files may contain unsaved changes " ) ;
2026-04-01 21:49:44 +03:00
gui . clipping_element < ScrollArea > ( " close file popup gui scroll area " , ScrollArea : : Options {
2026-03-31 18:41:19 +03:00
. scrollVertical = true ,
. clipVertical = true ,
2026-04-29 15:14:13 +03:00
. scrollbarY = ScrollArea : : ScrollbarType : : NORMAL ,
2026-03-31 18:41:19 +03:00
. innerContent = [ & ] ( const ScrollArea : : InnerContentParameters & ) {
CLAY_AUTO_ID ( {
. layout = {
. childGap = io . theme - > childGap1 ,
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
2026-01-18 13:39:49 +03:00
}
2026-03-31 18:41:19 +03:00
} ) {
size_t i = 0 ;
2026-03-30 18:51:14 +03:00
for ( auto & [ w , setToSave ] : closePopupData . worldsToClose ) {
auto wLock = w . lock ( ) ;
2026-03-31 18:41:19 +03:00
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
. childGap = io . theme - > childGap1 ,
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_CENTER } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT
} ,
. backgroundColor = convert_vec4 < Clay_Color > ( io . theme - > backColor2 ) ,
. cornerRadius = CLAY_CORNER_RADIUS ( io . theme - > windowCorners1 ) ,
} ) {
gui . new_id ( i , [ & ] {
checkbox_boolean ( gui , " set to save checkbox " , & setToSave ) ;
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_FIT ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. childGap = 0 ,
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_CENTER } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
}
} ) {
text_label ( gui , wLock - > name ) ;
text_label_light ( gui , wLock - > filePath . empty ( ) ? " Autosave in " + main . documentsPath . string ( ) : wLock - > filePath . string ( ) ) ;
}
} ) ;
2026-03-30 18:51:14 +03:00
}
2026-03-31 18:41:19 +03:00
+ + i ;
2026-03-27 09:51:53 +03:00
}
2026-03-30 18:51:14 +03:00
}
2026-03-31 18:41:19 +03:00
}
} ) ;
text_button ( gui , " Save " , " Save " , {
. wide = true ,
. onClick = [ & ] {
for ( auto & [ w , setToSave ] : closePopupData . worldsToClose ) {
auto wLock = w . lock ( ) ;
if ( setToSave ) {
if ( wLock - > filePath . empty ( ) )
wLock - > autosave_to_directory ( main . documentsPath ) ;
else
wLock - > save_to_file ( wLock - > filePath ) ;
}
2026-04-05 20:26:03 +03:00
main . set_tab_to_close ( wLock . get ( ) ) ;
2026-03-30 18:51:14 +03:00
}
2026-03-31 18:41:19 +03:00
closePopupData . worldsToClose . clear ( ) ;
if ( closePopupData . closeAppWhenDone )
main . setToQuit = true ;
}
2026-03-27 09:51:53 +03:00
} ) ;
2026-03-31 18:41:19 +03:00
text_button ( gui , " Discard All " , " Discard All " , {
. wide = true ,
. onClick = [ & ] {
2026-04-05 20:26:03 +03:00
for ( auto & [ w , setToSave ] : closePopupData . worldsToClose ) {
auto wLock = w . lock ( ) ;
if ( wLock )
main . set_tab_to_close ( wLock . get ( ) ) ;
}
2026-03-31 18:41:19 +03:00
closePopupData . worldsToClose . clear ( ) ;
if ( closePopupData . closeAppWhenDone )
main . setToQuit = true ;
}
} ) ;
text_button ( gui , " Cancel " , " Cancel " , {
. wide = true ,
. onClick = [ & ] {
closePopupData . worldsToClose . clear ( ) ;
closePopupData . closeAppWhenDone = false ;
}
} ) ;
} ) ;
2026-01-18 13:39:49 +03:00
}
2025-07-06 20:43:53 +03:00
void Toolbar : : save_func ( ) {
if ( main . world - > filePath = = std : : filesystem : : path ( ) )
save_as_func ( ) ;
2025-07-28 18:21:09 +03:00
else
2026-01-18 13:39:49 +03:00
main . world - > save_to_file ( main . world - > filePath ) ;
2025-07-06 20:43:53 +03:00
}
void Toolbar : : save_as_func ( ) {
# ifdef __EMSCRIPTEN__
optionsMenuOpen = true ;
optionsMenuType = SET_DOWNLOAD_NAME ;
# else
2026-01-14 07:33:54 +03:00
open_file_selector ( " Save " , { { " InfiniPaint Canvas " , World : : FILE_EXTENSION } } , [ w = make_weak_ptr ( main . world ) ] ( const std : : filesystem : : path & p , const auto & e ) {
2025-07-06 20:43:53 +03:00
auto world = w . lock ( ) ;
if ( world )
world - > save_to_file ( p ) ;
} , " " , true ) ;
# endif
}
2025-12-01 17:33:14 +03:00
void Toolbar : : paint_popup ( Vector2f popupPos ) {
2026-04-02 12:46:16 +03:00
using namespace GUIStuff ;
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
2026-04-02 12:46:16 +03:00
std : : shared_ptr < double > newRotationAngle = std : : make_shared < double > ( main . world - > drawData . cam . c . rotation ) ;
gui . set_z_index ( - 1 , [ & ] {
paint_circle_popup_menu ( gui , " paint circle popup " , popupPos , {
. rotationAngle = newRotationAngle . get ( ) ,
. selectedColor = main . world - > drawProg . get_foreground_color_ptr ( ) ,
2026-04-12 12:36:00 +03:00
. palette = main . conf . palettes [ paletteData . selectedPalette ] . colors ,
2026-04-02 12:46:16 +03:00
. onRotate = [ & , newRotationAngle ] {
main . world - > drawData . cam . c . rotate_about ( main . world - > drawData . cam . c . from_space ( main . window . size . cast < float > ( ) * 0.5f ) , * newRotationAngle - main . world - > drawData . cam . c . rotation ) ;
* newRotationAngle = main . world - > drawData . cam . c . rotation ;
gui . set_to_layout ( ) ;
} ,
. onPaletteClick = [ & ] {
gui . set_to_layout ( ) ;
} ,
. mouseButton = [ & ] ( const InputManager : : MouseButtonCallbackArgs & button , bool mouseHovering ) {
2026-04-02 13:45:46 +03:00
if ( ! mouseHovering & & button . down & & button . button ! = InputManager : : MouseButton : : RIGHT )
main . world - > drawProg . clear_right_click_popup ( ) ;
2026-04-02 12:46:16 +03:00
}
} ) ;
2025-12-01 17:33:14 +03:00
} ) ;
2025-07-06 20:43:53 +03:00
}
void Toolbar : : top_toolbar ( ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
auto & io = gui . io ;
2026-04-02 21:57:26 +03:00
gui . element < LayoutElement > ( " top menu bar " , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
2026-03-31 20:52:46 +03:00
CLAY ( lId , {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. padding = CLAY_PADDING_ALL ( static_cast < uint16_t > ( io . theme - > padding1 / 2 ) ) ,
. childGap = static_cast < uint16_t > ( io . theme - > childGap1 / 2 ) ,
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_CENTER } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT
} ,
. backgroundColor = convert_vec4 < Clay_Color > ( io . theme - > backColor1 ) ,
. cornerRadius = CLAY_CORNER_RADIUS ( io . theme - > windowCorners1 )
} ) {
2026-03-30 18:51:14 +03:00
global_log ( ) ;
2026-03-31 18:41:19 +03:00
2026-03-30 18:51:14 +03:00
auto icon_button_top_toolbar = [ & ] ( const char * id , const std : : string & svgPath , bool isSelected , const std : : function < void ( ) > & onClick ) {
2026-04-02 21:57:26 +03:00
return svg_icon_button ( gui , id , svgPath , {
2026-03-30 19:41:18 +03:00
. drawType = SelectableButton : : DrawType : : TRANSPARENT_ALL ,
2026-03-30 18:51:14 +03:00
. isSelected = isSelected ,
. onClick = onClick
2026-03-27 09:51:53 +03:00
} ) ;
2026-03-30 18:51:14 +03:00
} ;
2026-03-31 18:41:19 +03:00
2026-04-02 21:57:26 +03:00
Element * mainMenuButton = icon_button_top_toolbar ( " Main Menu Button " , " data/icons/menu.svg " , menuPopUpOpen , [ & ] {
menuPopUpOpen = ! menuPopUpOpen ;
2026-03-27 09:51:53 +03:00
} ) ;
2026-03-30 19:41:18 +03:00
2026-04-05 20:26:03 +03:00
std : : vector < MovableTabListData : : IconNamePair > tabNames ;
for ( size_t i = 0 ; i < main . worlds . size ( ) ; i + + ) {
auto & w = main . worlds [ i ] ;
bool shouldAddStarNextToName = w - > should_ask_before_closing ( ) & & ! w - > netServer & & ! w - > netClient ;
tabNames . emplace_back ( w - > netObjMan . is_connected ( ) ? " data/icons/network.svg " : " " , w - > name + ( shouldAddStarNextToName ? " * " : " " ) ) ;
}
2026-03-30 19:41:18 +03:00
2026-04-05 20:26:03 +03:00
gui . element < MovableTabList > ( " file tab list " , MovableTabListData {
. tabNames = tabNames ,
. selectedTab = main . worldIndex ,
. changeSelectedTab = [ & ] ( size_t i ) {
main . switch_to_tab ( i ) ;
} ,
. closeTab = [ & ] ( size_t i ) {
if ( main . worlds [ i ] - > should_ask_before_closing ( ) )
add_world_to_close_popup_data ( main . worlds [ i ] ) ;
else
main . set_tab_to_close ( main . worlds [ i ] . get ( ) ) ;
2026-03-30 19:41:18 +03:00
}
2026-04-05 20:26:03 +03:00
} ) ;
2026-03-30 18:51:14 +03:00
if ( ! main . world - > clientStillConnecting ) {
if ( main . world - > netObjMan . is_connected ( ) ) {
icon_button_top_toolbar ( " Player List Toggle Button " , " data/icons/list.svg " , playerMenuOpen , [ & ] {
playerMenuOpen = ! playerMenuOpen ;
} ) ;
2026-01-09 21:56:58 +03:00
}
2026-03-30 18:51:14 +03:00
icon_button_top_toolbar ( " Menu Undo Button " , " data/icons/undo.svg " , false , [ & ] {
main . world - > undo_with_checks ( ) ;
} ) ;
icon_button_top_toolbar ( " Menu Redo Button " , " data/icons/redo.svg " , false , [ & ] {
main . world - > redo_with_checks ( ) ;
} ) ;
2026-05-13 14:31:35 +03:00
Element * gridMenuButton = icon_button_top_toolbar ( " Grids Button " , " data/icons/grid.svg " , gridMenuPopupOpen , [ & ] {
if ( gridMenuPopupOpen )
2026-03-30 18:51:14 +03:00
stop_displaying_grid_menu ( ) ;
2026-04-06 12:23:46 +03:00
else
2026-05-13 14:31:35 +03:00
gridMenuPopupOpen = true ;
2026-03-30 18:51:14 +03:00
} ) ;
2026-04-07 16:57:11 +03:00
Element * layerMenuButton = icon_button_top_toolbar ( " Layer Menu Button " , " data/icons/layer.svg " , layerMenuPopupOpen , [ & ] {
if ( layerMenuPopupOpen )
stop_displaying_layer_menu ( ) ;
else
2026-03-30 18:51:14 +03:00
layerMenuPopupOpen = true ;
} ) ;
2026-04-06 20:16:25 +03:00
Element * bookmarkMenuButton = icon_button_top_toolbar ( " Bookmark Menu Button " , " data/icons/bookmark.svg " , bookmarkMenuPopupOpen , [ & ] {
if ( bookmarkMenuPopupOpen )
stop_displaying_bookmark_menu ( ) ;
else
2026-03-30 18:51:14 +03:00
bookmarkMenuPopupOpen = true ;
} ) ;
2025-07-06 20:43:53 +03:00
2026-05-13 14:31:35 +03:00
if ( gridMenuPopupOpen )
2026-04-06 12:23:46 +03:00
grid_menu ( gridMenuButton ) ;
2026-03-30 18:51:14 +03:00
if ( bookmarkMenuPopupOpen )
2026-04-06 20:16:25 +03:00
bookmark_menu ( bookmarkMenuButton ) ;
2026-03-30 18:51:14 +03:00
if ( layerMenuPopupOpen )
2026-04-07 16:57:11 +03:00
layer_menu ( layerMenuButton ) ;
2026-03-30 18:51:14 +03:00
}
if ( menuPopUpOpen ) {
2026-04-02 18:23:36 +03:00
gui . set_z_index ( 5 , [ & ] {
2026-04-02 21:57:26 +03:00
gui . element < LayoutElement > ( " main menu popup " , [ & ] ( LayoutElement * , const Clay_ElementId & id ) {
2026-04-02 18:23:36 +03:00
CLAY ( id , {
. layout = {
. sizing = { . width = CLAY_SIZING_FIT ( 100 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
. childGap = 1 ,
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
. backgroundColor = convert_vec4 < Clay_Color > ( io . theme - > backColor1 ) ,
. cornerRadius = CLAY_CORNER_RADIUS ( io . theme - > windowCorners1 ) ,
. floating = { . offset = { . x = 0 , . y = static_cast < float > ( io . theme - > padding1 ) } , . zIndex = gui . get_z_index ( ) , . attachPoints = { . element = CLAY_ATTACH_POINT_LEFT_TOP , . parent = CLAY_ATTACH_POINT_LEFT_BOTTOM } , . attachTo = CLAY_ATTACH_TO_PARENT }
} ) {
auto menu_popup_text_button = [ & ] ( const char * id , const char * str , const std : : function < void ( ) > & onClick ) {
text_button ( gui , id , str , {
. drawType = SelectableButton : : DrawType : : TRANSPARENT_ALL ,
. wide = true ,
. centered = false ,
. onClick = [ & , onClick ] {
onClick ( ) ;
menuPopUpOpen = false ;
}
} ) ;
} ;
menu_popup_text_button ( " new file local " , " New File " , [ & ] {
2026-04-11 16:36:24 +03:00
CustomEvents : : emit_event < CustomEvents : : OpenInfiniPaintFileEvent > ( {
2026-04-02 18:23:36 +03:00
. isClient = false
2026-04-05 20:26:03 +03:00
} ) ;
2026-03-30 18:51:14 +03:00
} ) ;
2026-04-02 18:23:36 +03:00
menu_popup_text_button ( " open file " , " Open " , [ & ] { open_world_file ( false , " " , " " ) ; } ) ;
if ( ! main . world - > clientStillConnecting ) {
menu_popup_text_button ( " save file " , " Save " , [ & ] { save_func ( ) ; } ) ;
menu_popup_text_button ( " save as file " , " Save As " , [ & ] { save_as_func ( ) ; } ) ;
menu_popup_text_button ( " screenshot " , " Screenshot " , [ & ] { main . world - > drawProg . switch_to_tool ( DrawingProgramToolType : : SCREENSHOT ) ; } ) ;
menu_popup_text_button ( " add image or file to canvas " , " Add Image/File to Canvas " , [ & ] {
# ifdef __EMSCRIPTEN__
emscripten_browser_file : : upload ( " * " , [ ] ( std : : string const & fileName , std : : string const & mimeType , std : : string_view buffer , void * callbackData ) {
if ( ! buffer . empty ( ) ) {
2026-04-11 16:36:24 +03:00
CustomEvents : : emit_event < CustomEvents : : AddFileToCanvasEvent > ( {
. type = CustomEvents : : AddFileToCanvasEvent : : Type : : BUFFER ,
. name = fileName ,
. buffer = std : : string ( buffer )
} ) ;
2026-04-02 18:23:36 +03:00
}
2026-04-11 16:36:24 +03:00
} , & main ) ;
2026-04-02 18:23:36 +03:00
# else
2026-04-11 16:36:24 +03:00
open_file_selector ( " Open File " , { { " Any File " , " * " } } , [ & ] ( const std : : filesystem : : path & p , const auto & e ) {
CustomEvents : : emit_event < CustomEvents : : AddFileToCanvasEvent > ( {
. type = CustomEvents : : AddFileToCanvasEvent : : Type : : PATH ,
. filePath = p ,
. pos = main . window . size . cast < float > ( ) / 2.0f
} ) ;
2026-04-02 18:23:36 +03:00
} ) ;
# endif
} ) ;
if ( main . world - > netObjMan . is_connected ( ) ) {
menu_popup_text_button ( " lobby info " , " Lobby Info " , [ & ] {
optionsMenuOpen = true ;
optionsMenuType = LOBBY_INFO_MENU ;
2026-03-30 18:51:14 +03:00
} ) ;
2026-04-02 18:23:36 +03:00
}
menu_popup_text_button ( " start hosting " , " Host " , [ & ] {
serverLocalID = NetLibrary : : get_random_server_local_id ( ) ;
serverToConnectTo = NetLibrary : : get_global_id ( ) + serverLocalID ;
2026-03-30 18:51:14 +03:00
optionsMenuOpen = true ;
2026-04-02 18:23:36 +03:00
optionsMenuType = HOST_MENU ;
} ) ;
menu_popup_text_button ( " canvas specific settings " , " Canvas Settings " , [ & ] {
optionsMenuOpen = true ;
optionsMenuType = CANVAS_SETTINGS_MENU ;
2026-03-27 09:51:53 +03:00
} ) ;
2026-03-30 18:51:14 +03:00
}
2026-04-02 18:23:36 +03:00
menu_popup_text_button ( " start connecting " , " Connect " , [ & ] {
serverToConnectTo . clear ( ) ;
2026-03-27 09:51:53 +03:00
optionsMenuOpen = true ;
2026-04-02 18:23:36 +03:00
optionsMenuType = CONNECT_MENU ;
2026-03-30 18:51:14 +03:00
} ) ;
2026-04-02 18:23:36 +03:00
menu_popup_text_button ( " open options " , " Settings " , [ & ] {
2026-03-30 18:51:14 +03:00
optionsMenuOpen = true ;
2026-04-02 18:23:36 +03:00
optionsMenuType = GENERAL_SETTINGS_MENU ;
2026-01-09 21:56:58 +03:00
} ) ;
2026-04-02 18:23:36 +03:00
menu_popup_text_button ( " about menu button " , " About " , [ & ] {
optionsMenuOpen = true ;
optionsMenuType = ABOUT_MENU ;
2026-03-30 18:51:14 +03:00
} ) ;
2026-05-17 19:37:19 +03:00
menu_popup_text_button ( " website menu button " , " Website " , [ & ] {
SDL_OpenURL ( " https://infinipaint.com/ " ) ;
} ) ;
menu_popup_text_button ( " donate menu button " , " Donate " , [ & ] {
SDL_OpenURL ( " https://infinipaint.com/donate.html " ) ;
} ) ;
2026-04-02 18:23:36 +03:00
# ifndef __EMSCRIPTEN__
menu_popup_text_button ( " quit button " , " Quit " , [ & ] {
if ( main . app_close_requested ( ) )
main . setToQuit = true ;
} ) ;
# endif
2026-03-30 18:51:14 +03:00
}
2026-04-02 18:23:36 +03:00
} , LayoutElement : : Callbacks {
2026-04-28 20:28:21 +03:00
. onClick = [ & , mainMenuButton ] ( LayoutElement * l , const InputManager : : MouseButtonCallbackArgs & button ) {
2026-04-02 21:57:26 +03:00
if ( ! l - > mouseHovering & & button . down & & ! mainMenuButton - > mouseHovering ) {
2026-04-02 18:23:36 +03:00
menuPopUpOpen = false ;
gui . set_to_layout ( ) ;
}
}
} ) ;
2026-03-30 18:51:14 +03:00
} ) ;
}
2026-03-31 20:52:46 +03:00
}
} ) ;
2025-07-06 20:43:53 +03:00
}
void Toolbar : : web_version_welcome ( ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
auto & io = gui . io ;
2026-03-27 09:51:53 +03:00
CLAY_AUTO_ID ( {
2025-07-06 20:43:53 +03:00
. layout = {
. sizing = { . width = CLAY_SIZING_FIXED ( 700 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
2026-03-31 13:17:58 +03:00
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
. childGap = io . theme - > childGap1 ,
2025-07-06 20:43:53 +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 ) ,
2025-11-01 20:47:34 +03:00
. floating = { . attachPoints = { . element = CLAY_ATTACH_POINT_CENTER_CENTER , . parent = CLAY_ATTACH_POINT_CENTER_CENTER } , . attachTo = CLAY_ATTACH_TO_PARENT }
2025-07-06 20:43:53 +03:00
} ) {
2026-03-30 18:51:14 +03:00
gui . new_id ( " web version welcome gui " , [ & ] {
text_label_centered ( gui , " Welcome to the web version of InfiniPaint! " ) ;
text_label ( gui ,
2025-07-06 20:43:53 +03:00
R " (This version contains more known issues than the native version of the app. This includes:
- Rare crashes
- If this browser tab is unfocused , or the window is minimized , any InfiniPaint tabs connected online ( whether host or client ) will be disconnected
- 4 GB memory limit . Might be a problem if you ' re uploading many files / images
2025-11-17 16:08:42 +03:00
- Not multithreaded
- Can ' t access local fonts
2025-07-06 20:43:53 +03:00
If you like this app , consider downloading the native version for your system ) " );
2026-03-30 18:51:14 +03:00
text_button ( gui , " got it " , " Got It " , {
. wide = true ,
. onClick = [ & ] {
2026-03-31 13:17:58 +03:00
main . conf . viewWebVersionWelcome = false ;
2026-03-30 18:51:14 +03:00
}
} ) ;
2026-03-27 09:51:53 +03:00
} ) ;
2025-07-06 20:43:53 +03:00
}
}
2026-05-13 14:31:35 +03:00
void Toolbar : : center_message ( const char * id , const std : : string & m ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
auto & io = gui . io ;
2026-05-13 14:31:35 +03:00
gui . set_z_index ( gui . get_z_index ( ) + 1 , [ & ] {
gui . element < LayoutElement > ( id , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
CLAY ( lId , {
. layout = {
. sizing = { . width = CLAY_SIZING_FIT ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
. childGap = io . theme - > childGap1 ,
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
. backgroundColor = convert_vec4 < Clay_Color > ( io . theme - > backColor1 ) ,
. cornerRadius = CLAY_CORNER_RADIUS ( io . theme - > windowCorners1 ) ,
. floating = {
. zIndex = gui . get_z_index ( ) ,
. attachPoints = { . element = CLAY_ATTACH_POINT_CENTER_CENTER , . parent = CLAY_ATTACH_POINT_CENTER_CENTER } ,
. attachTo = CLAY_ATTACH_TO_PARENT ,
}
} ) {
text_label ( gui , m ) ;
}
} ) ;
} ) ;
2026-01-09 21:56:58 +03:00
}
2025-10-01 20:08:01 +03:00
void Toolbar : : update_notification_gui ( ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
2026-03-31 18:41:19 +03:00
center_obstructing_window_gui ( " Update notifications GUI " , CLAY_SIZING_FIXED ( 700 ) , CLAY_SIZING_FIT ( 0 ) , [ & ] {
2026-03-30 18:51:14 +03:00
gui . new_id ( " update notification gui " , [ & ] {
2026-05-17 17:32:05 +03:00
text_label_centered ( gui , " Update v " + main . updateCheckerData . newVersionStr + " available! " ) ;
2026-03-30 18:51:14 +03:00
text_button ( gui , " download " , " Open download page in web browser " , {
. wide = true ,
. onClick = [ & ] {
2026-05-17 17:32:05 +03:00
SDL_OpenURL ( MainProgram : : UPDATE_DOWNLOAD_URL ) ;
main . updateCheckerData . showGui = false ;
2026-03-30 18:51:14 +03:00
}
} ) ;
text_button ( gui , " ignore forever " , " Ignore and don't notify again (can be changed in settings) " , {
. wide = true ,
. onClick = [ & ] {
2026-03-31 13:17:58 +03:00
main . conf . checkForUpdates = false ;
2026-05-17 17:32:05 +03:00
main . updateCheckerData . showGui = false ;
2026-03-30 18:51:14 +03:00
}
} ) ;
text_button ( gui , " ignore for now " , " Ignore for now " , {
. wide = true ,
. onClick = [ & ] {
2026-05-17 17:32:05 +03:00
main . updateCheckerData . showGui = false ;
2026-03-30 18:51:14 +03:00
}
} ) ;
2026-03-27 09:51:53 +03:00
} ) ;
2026-03-31 18:41:19 +03:00
} ) ;
2025-10-01 20:08:01 +03:00
}
2026-04-06 12:23:46 +03:00
void Toolbar : : grid_menu ( Element * gridMenuButton ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
auto & io = gui . io ;
2025-12-31 11:59:53 +03:00
if ( main . world - > gridMan . grids ) {
2026-04-06 12:23:46 +03:00
gui . set_z_index ( gui . get_z_index ( ) + 1 , [ & ] {
gui . element < LayoutElement > ( " grid menu " , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
CLAY ( lId , {
. layout = {
. sizing = { . width = CLAY_SIZING_FIT ( 300 ) , . height = CLAY_SIZING_FIT ( 0 , 600 ) } ,
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
. childGap = io . theme - > childGap1 ,
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
. backgroundColor = convert_vec4 < Clay_Color > ( io . theme - > backColor1 ) ,
. cornerRadius = CLAY_CORNER_RADIUS ( io . theme - > windowCorners1 ) ,
2026-04-06 20:16:25 +03:00
. floating = { . offset = { . x = 0 , . y = static_cast < float > ( io . theme - > padding1 ) } , . zIndex = gui . get_z_index ( ) , . attachPoints = { . element = CLAY_ATTACH_POINT_RIGHT_TOP , . parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM } , . attachTo = CLAY_ATTACH_TO_PARENT }
2026-04-06 12:23:46 +03:00
} ) {
text_label_centered ( gui , " Grids " ) ;
2026-05-13 14:31:35 +03:00
main . world - > gridMan . setup_list_gui ( [ & ] { stop_displaying_grid_menu ( ) ; } ) ;
2026-04-06 12:23:46 +03:00
}
} , LayoutElement : : Callbacks {
2026-04-28 20:28:21 +03:00
. onClick = [ & , gridMenuButton ] ( LayoutElement * l , const InputManager : : MouseButtonCallbackArgs & button ) {
2026-04-06 12:23:46 +03:00
if ( ! l - > mouseHovering & & ! l - > childMouseHovering & & ! gridMenuButton - > mouseHovering & & button . down )
stop_displaying_grid_menu ( ) ;
}
} ) ;
2026-03-30 18:51:14 +03:00
} ) ;
2025-09-06 19:06:30 +03:00
}
}
2025-09-12 09:54:49 +03:00
void Toolbar : : stop_displaying_grid_menu ( ) {
2026-05-13 14:31:35 +03:00
gridMenuPopupOpen = false ;
main . world - > gridMan . refresh_gui_data ( ) ;
2026-04-06 12:23:46 +03:00
main . g . gui . set_to_layout ( ) ;
2025-09-12 09:54:49 +03:00
}
2026-04-06 20:16:25 +03:00
void Toolbar : : stop_displaying_bookmark_menu ( ) {
main . world - > bMan . refresh_gui_data ( ) ;
bookmarkMenuPopupOpen = false ;
main . g . gui . set_to_layout ( ) ;
}
2026-04-07 16:57:11 +03:00
void Toolbar : : stop_displaying_layer_menu ( ) {
main . world - > drawProg . layerMan . listGUI . refresh_gui_data ( ) ;
layerMenuPopupOpen = false ;
main . g . gui . set_to_layout ( ) ;
}
2026-04-06 20:16:25 +03:00
void Toolbar : : bookmark_menu ( Element * bookmarkMenuButton ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
auto & io = gui . io ;
2026-04-06 20:16:25 +03:00
gui . set_z_index ( gui . get_z_index ( ) + 1 , [ & ] {
2026-04-07 16:57:11 +03:00
gui . element < LayoutElement > ( " bookmark menu " , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
2026-04-06 20:16:25 +03:00
CLAY ( lId , {
. layout = {
. sizing = { . width = CLAY_SIZING_FIT ( 300 ) , . height = CLAY_SIZING_FIT ( 0 , 600 ) } ,
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
. childGap = io . theme - > childGap1 ,
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
. backgroundColor = convert_vec4 < Clay_Color > ( io . theme - > backColor1 ) ,
. cornerRadius = CLAY_CORNER_RADIUS ( io . theme - > windowCorners1 ) ,
. floating = { . offset = { . x = 0 , . y = static_cast < float > ( io . theme - > padding1 ) } , . zIndex = gui . get_z_index ( ) , . attachPoints = { . element = CLAY_ATTACH_POINT_RIGHT_TOP , . parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM } , . attachTo = CLAY_ATTACH_TO_PARENT }
} ) {
text_label_centered ( gui , " Bookmarks " ) ;
2026-04-07 16:57:11 +03:00
main . world - > bMan . setup_list_gui ( ) ;
2026-04-06 20:16:25 +03:00
}
} , LayoutElement : : Callbacks {
2026-04-28 20:28:21 +03:00
. onClick = [ & , bookmarkMenuButton ] ( LayoutElement * l , const InputManager : : MouseButtonCallbackArgs & button ) {
2026-04-06 20:16:25 +03:00
if ( ! l - > mouseHovering & & ! l - > childMouseHovering & & ! bookmarkMenuButton - > mouseHovering & & button . down )
stop_displaying_bookmark_menu ( ) ;
}
} ) ;
2026-03-30 18:51:14 +03:00
} ) ;
2025-07-06 20:43:53 +03:00
}
2026-04-07 16:57:11 +03:00
void Toolbar : : layer_menu ( Element * layerMenuButton ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
auto & io = gui . io ;
2026-04-07 16:57:11 +03:00
gui . set_z_index ( gui . get_z_index ( ) + 1 , [ & ] {
gui . element < LayoutElement > ( " layer menu " , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
CLAY ( lId , {
. layout = {
. sizing = { . width = CLAY_SIZING_FIT ( 300 ) , . height = CLAY_SIZING_FIT ( 0 , 600 ) } ,
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
. childGap = io . theme - > childGap1 ,
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
. backgroundColor = convert_vec4 < Clay_Color > ( io . theme - > backColor1 ) ,
. cornerRadius = CLAY_CORNER_RADIUS ( io . theme - > windowCorners1 ) ,
. floating = { . offset = { . x = 0 , . y = static_cast < float > ( io . theme - > padding1 ) } , . zIndex = gui . get_z_index ( ) , . attachPoints = { . element = CLAY_ATTACH_POINT_RIGHT_TOP , . parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM } , . attachTo = CLAY_ATTACH_TO_PARENT }
} ) {
text_label_centered ( gui , " Layers " ) ;
main . world - > drawProg . layerMan . listGUI . setup_list_gui ( ) ;
}
} , LayoutElement : : Callbacks {
2026-04-28 20:28:21 +03:00
. onClick = [ & , layerMenuButton ] ( LayoutElement * l , const InputManager : : MouseButtonCallbackArgs & button ) {
2026-04-07 16:57:11 +03:00
if ( ! l - > mouseHovering & & ! l - > childMouseHovering & & ! layerMenuButton - > mouseHovering & & button . down )
stop_displaying_layer_menu ( ) ;
}
} ) ;
2026-03-30 18:51:14 +03:00
} ) ;
2025-12-25 17:18:28 +03:00
}
2026-04-09 16:30:28 +03:00
RichText : : TextData Toolbar : : build_paragraph_from_chat_message ( const ChatMessage & message , float alpha ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
auto & io = gui . io ;
2026-04-09 16:30:28 +03:00
RichText : : TextData toRet ;
auto & par = toRet . paragraphs . emplace_back ( ) ;
2025-10-16 09:38:58 +03:00
2026-04-09 16:30:28 +03:00
RichText : : PositionedTextStyleMod & positionedModInit = toRet . tStyleMods . emplace_back ( ) ;
positionedModInit . pos = { 0 , 0 } ;
positionedModInit . mods [ RichText : : TextStyleModifier : : ModifierType : : WEIGHT ] = std : : make_shared < RichText : : WeightTextStyleModifier > ( SkFontStyle : : Weight : : kBold_Weight ) ;
positionedModInit . mods [ RichText : : TextStyleModifier : : ModifierType : : COLOR ] = std : : make_shared < RichText : : ColorTextStyleModifier > ( convert_vec4 < Vector4f > ( color_mul_alpha ( message . type = = ChatMessage : : JOIN ? io . theme - > warningColor : io . theme - > frontColor1 , alpha ) ) ) ;
if ( message . type = = ChatMessage : : JOIN )
par . text + = message . name + " " ;
else
par . text + = " [ " + message . name + " ] " ;
RichText : : PositionedTextStyleMod & positionedModMessage = toRet . tStyleMods . emplace_back ( ) ;
positionedModMessage . pos = { 0 , par . text . size ( ) } ;
positionedModMessage . mods [ RichText : : TextStyleModifier : : ModifierType : : WEIGHT ] = std : : make_shared < RichText : : WeightTextStyleModifier > ( SkFontStyle : : Weight : : kNormal_Weight ) ;
par . text + = message . message ;
2025-10-16 09:38:58 +03:00
2026-04-09 16:30:28 +03:00
return toRet ;
2025-10-16 09:38:58 +03:00
}
2025-07-06 20:43:53 +03:00
void Toolbar : : chat_box ( ) {
2026-04-09 16:30:28 +03:00
auto & gui = main . g . gui ;
auto & io = gui . io ;
constexpr float CHATBOX_WIDTH = 700 ;
if ( main . world - > netObjMan . is_connected ( ) ) {
gui . element < LayoutElement > ( " Infinipaint chat box open button " , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
CLAY ( lId , {
. layout = {
. layoutDirection = CLAY_LEFT_TO_RIGHT
} ,
. floating = { . offset = { static_cast < float > ( io . theme - > padding1 ) , - static_cast < float > ( io . theme - > padding1 ) } , . zIndex = gui . get_z_index ( ) , . attachPoints = { . element = CLAY_ATTACH_POINT_LEFT_BOTTOM , . parent = CLAY_ATTACH_POINT_LEFT_BOTTOM } , . attachTo = CLAY_ATTACH_TO_PARENT }
} ) {
svg_icon_button ( gui , " Chat open button " , " data/icons/chat.svg " , {
. onClick = [ & ] {
chatboxOpen = ! chatboxOpen ;
chatMessageInput . clear ( ) ;
}
} ) ;
}
} ) ;
}
gui . element < LayoutElement > ( " Infinipaint chat box " , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
CLAY ( lId , {
. layout = {
. sizing = { . width = CLAY_SIZING_FIXED ( CHATBOX_WIDTH ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. childGap = 0 ,
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_BOTTOM } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
. floating = { . offset = { 60 + static_cast < float > ( io . theme - > padding1 ) , - static_cast < float > ( io . theme - > padding1 ) } , . zIndex = gui . get_z_index ( ) , . attachPoints = { . element = CLAY_ATTACH_POINT_LEFT_BOTTOM , . parent = CLAY_ATTACH_POINT_LEFT_BOTTOM } , . attachTo = CLAY_ATTACH_TO_PARENT }
} ) {
if ( chatboxOpen ) {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. padding = CLAY_PADDING_ALL ( 0 ) ,
. childGap = 0 ,
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_BOTTOM } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
. backgroundColor = convert_vec4 < Clay_Color > ( io . theme - > backColor1 )
} ) {
int id = 0 ;
for ( auto & chatMessage : main . world - > chatMessages | std : : views : : reverse ) {
gui . new_id ( id + + , [ & ] {
gui . element < TextParagraph > ( " text " , TextParagraph : : Data {
. text = build_paragraph_from_chat_message ( chatMessage , 1.0f ) ,
2026-04-25 19:01:19 +03:00
. maxGrowX = CHATBOX_WIDTH ,
. ellipsis = false
2026-04-09 16:30:28 +03:00
} ) ;
} ) ;
}
}
left_to_right_line_layout ( gui , [ & ] {
input_text ( gui , " message input " , & chatMessageInput , {
. onEnter = [ & ] {
if ( ! chatMessageInput . empty ( ) )
main . world - > send_chat_message ( chatMessageInput ) ;
chatboxOpen = false ;
} ,
. onDeselect = [ & ] {
chatboxOpen = false ;
gui . set_to_layout ( ) ;
}
} ) - > select ( ) ;
text_button ( gui , " send button " , " Send " , {
. instantResponse = true ,
. onClick = [ & ] {
if ( ! chatMessageInput . empty ( ) )
main . world - > send_chat_message ( chatMessageInput ) ;
chatboxOpen = false ;
}
} ) ;
} ) ;
}
else {
gui . new_id ( " Message popups " , [ & ] {
int id = 0 ;
for ( auto & chatMessage : main . world - > chatMessages | std : : views : : reverse ) {
chatMessage . time . update_time_since ( ) ;
if ( chatMessage . time < ChatMessage : : DISPLAY_TIME ) {
gui . new_id ( id + + , [ & ] {
float a = 1.0f - lerp_time < float > ( chatMessage . time , ChatMessage : : DISPLAY_TIME , ChatMessage : : FADE_START_TIME ) ;
gui . element < LayoutElement > ( " Chat message " , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
CLAY ( lId , {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. padding = CLAY_PADDING_ALL ( 0 ) ,
. childGap = 0 ,
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
. backgroundColor = convert_vec4 < Clay_Color > ( color_mul_alpha ( io . theme - > backColor1 , a ) ) ,
} ) {
gui . element < TextParagraph > ( " message " , TextParagraph : : Data {
. text = build_paragraph_from_chat_message ( chatMessage , a ) ,
2026-04-25 19:01:19 +03:00
. maxGrowX = CHATBOX_WIDTH ,
. ellipsis = false
2026-04-09 16:30:28 +03:00
} ) ;
}
} ) ;
} ) ;
}
}
} ) ;
}
}
} ) ;
2025-07-06 20:43:53 +03:00
}
void Toolbar : : global_log ( ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
auto & io = gui . io ;
2026-04-07 19:15:58 +03:00
gui . new_id ( " Global log popup list " , [ & ] {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_FIXED ( 300 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. childGap = io . theme - > childGap1 ,
. childAlignment = { . x = CLAY_ALIGN_X_RIGHT , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
. floating = { . offset = { 0 , 10 } , . attachPoints = { . element = CLAY_ATTACH_POINT_RIGHT_TOP , . parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM } , . attachTo = CLAY_ATTACH_TO_PARENT }
} ) {
for ( size_t i = 0 ; i < main . logMessages . size ( ) ; i + + ) {
auto & logM = main . logMessages [ i ] ;
logM . time . update_time_since ( ) ;
2026-04-12 12:36:00 +03:00
if ( logM . time < UserLogMessage : : DISPLAY_TIME ) {
2026-05-16 21:56:58 +03:00
if ( logM . whereToDisplay = = UserLogMessage : : DISPLAY_PHONE_ONLY )
continue ;
2026-04-12 12:36:00 +03:00
float a = 1.0f - lerp_time < float > ( logM . time , UserLogMessage : : DISPLAY_TIME , UserLogMessage : : FADE_START_TIME ) ;
2026-04-07 19:15:58 +03:00
gui . new_id ( i , [ & ] {
gui . element < LayoutElement > ( " Global log message " , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
CLAY ( lId , {
. layout = {
. sizing = { . width = CLAY_SIZING_FIT ( 300 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
. childGap = 0 ,
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
. backgroundColor = convert_vec4 < Clay_Color > ( color_mul_alpha ( io . theme - > backColor1 , a ) ) ,
. cornerRadius = CLAY_CORNER_RADIUS ( io . theme - > windowCorners1 )
} ) {
SkColor4f c { 0 , 0 , 0 , 0 } ;
switch ( logM . color ) {
2026-04-12 12:36:00 +03:00
case UserLogMessage : : COLOR_NORMAL :
2026-04-07 19:15:58 +03:00
c = io . theme - > frontColor1 ;
break ;
2026-04-12 12:36:00 +03:00
case UserLogMessage : : COLOR_ERROR :
2026-04-07 19:15:58 +03:00
c = io . theme - > errorColor ;
break ;
}
text_label_color ( gui , logM . text , color_mul_alpha ( c , a ) ) ;
2026-04-05 22:01:30 +03:00
}
2026-04-07 19:15:58 +03:00
} ) ;
2026-04-05 22:01:30 +03:00
} ) ;
2026-04-07 19:15:58 +03:00
}
else
break ;
2026-04-05 22:01:30 +03:00
}
}
2026-04-07 19:15:58 +03:00
} ) ;
2025-07-06 20:43:53 +03:00
}
void Toolbar : : drawing_program_gui ( ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
auto & io = gui . io ;
2025-12-02 16:09:30 +03:00
CLAY_AUTO_ID ( {
2025-07-06 20:43:53 +03:00
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
2026-03-31 13:17:58 +03:00
. childGap = io . theme - > childGap1 ,
2025-07-06 20:43:53 +03:00
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_CENTER } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT
} ,
} ) {
2026-04-12 12:36:00 +03:00
main . world - > drawProg . toolbar_gui ( * this ) ;
2026-04-02 18:23:36 +03:00
2026-05-12 15:19:43 +03:00
colorLeft = main . world - > drawProg . color_picker_color ( colorLeft ) ;
2026-04-02 18:23:36 +03:00
if ( colorLeft )
2026-04-06 12:23:46 +03:00
color_picker_window ( " Drawing program gui color picker left " , & colorLeft , colorLeftButton , colorLeftData ) ;
2025-12-02 16:09:30 +03:00
CLAY_AUTO_ID ( {
2025-07-06 20:43:53 +03:00
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) }
}
} ) { }
2026-05-12 15:19:43 +03:00
colorRight = main . world - > drawProg . color_picker_color ( colorRight ) ;
2026-04-02 18:23:36 +03:00
if ( colorRight )
2026-04-06 12:23:46 +03:00
color_picker_window ( " Drawing program gui color picker right " , & colorRight , colorRightButton , colorRightData ) ;
2026-04-02 18:23:36 +03:00
2026-04-12 12:36:00 +03:00
main . world - > drawProg . tool_options_gui ( * this ) ;
2025-07-06 20:43:53 +03:00
}
}
2026-04-06 12:23:46 +03:00
void Toolbar : : color_picker_window ( const char * id , Vector4f * * color , GUIStuff : : Element * b , const ColorSelectorData & colorSelectorData ) {
2026-04-02 18:23:36 +03:00
auto & gui = main . g . gui ;
CLAY_AUTO_ID ( {
. layout = {
. padding = { . top = 40 , . bottom = 40 }
}
} ) {
2026-04-02 21:57:26 +03:00
main . g . gui . element < LayoutElement > ( id , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
2026-04-02 18:23:36 +03:00
CLAY ( lId , {
. layout = {
. sizing = { . width = CLAY_SIZING_FIT ( 300 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. padding = CLAY_PADDING_ALL ( gui . io . theme - > padding1 ) ,
. childGap = gui . io . theme - > childGap1 ,
2026-05-03 11:36:32 +03:00
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_TOP } ,
2026-04-02 18:23:36 +03:00
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
. backgroundColor = convert_vec4 < Clay_Color > ( gui . io . theme - > backColor1 ) ,
. cornerRadius = CLAY_CORNER_RADIUS ( gui . io . theme - > windowCorners1 )
} ) {
color_picker_items ( gui , " colorpicker " , * color , {
2026-04-04 19:53:45 +03:00
. onEdit = colorSelectorData . onChange ,
. onSelect = colorSelectorData . onSelect ,
. onDeselect = colorSelectorData . onDeselect ,
} ) ;
color_palette ( " colorpickerpalette " , * color , [ colorSelectorData ] {
if ( colorSelectorData . onSelect ) colorSelectorData . onSelect ( ) ;
if ( colorSelectorData . onChange ) colorSelectorData . onChange ( ) ;
if ( colorSelectorData . onDeselect ) colorSelectorData . onDeselect ( ) ;
2026-04-02 18:23:36 +03:00
} ) ;
}
} , LayoutElement : : Callbacks {
2026-04-28 20:28:21 +03:00
. onClick = [ & , b , color ] ( LayoutElement * l , const InputManager : : MouseButtonCallbackArgs & button ) {
2026-04-06 12:23:46 +03:00
if ( ! l - > mouseHovering & & ! l - > childMouseHovering & & ! b - > mouseHovering & & button . down ) {
* color = nullptr ;
2026-04-02 18:23:36 +03:00
main . g . gui . set_to_layout ( ) ;
}
}
} ) ;
}
}
2026-04-03 09:52:45 +03:00
void Toolbar : : color_palette ( const char * id , Vector4f * color , const std : : function < void ( ) > & onChange ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
auto & io = gui . io ;
2026-03-30 18:51:14 +03:00
gui . new_id ( id , [ & ] {
2026-04-12 12:36:00 +03:00
auto & palette = main . conf . palettes [ paletteData . selectedPalette ] . colors ;
2026-03-27 09:51:53 +03:00
2026-04-29 18:53:53 +03:00
gui . clipping_element < ScrollArea > ( " color palette scroll area " , ScrollArea : : Options {
2026-04-03 09:52:45 +03:00
. scrollVertical = true ,
. clipVertical = true ,
2026-04-29 15:14:13 +03:00
. scrollbarY = ScrollArea : : ScrollbarType : : NORMAL ,
2026-04-03 09:52:45 +03:00
. innerContent = [ & ] ( const ScrollArea : : InnerContentParameters & ) {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
. childGap = io . theme - > childGap1 ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
}
} ) {
size_t i = 0 ;
size_t nextID = 0 ;
while ( i < palette . size ( ) ) {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_FIXED ( BIG_BUTTON_SIZE ) } ,
. childGap = io . theme - > childGap1 ,
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_CENTER } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT
}
} ) {
while ( i < palette . size ( ) ) {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_FIXED ( BIG_BUTTON_SIZE ) , . height = CLAY_SIZING_FIXED ( BIG_BUTTON_SIZE ) }
}
} ) {
auto newC = std : : make_shared < Vector3f > ( palette [ i ] . x ( ) , palette [ i ] . y ( ) , palette [ i ] . z ( ) ) ;
gui . new_id ( nextID + + , [ & ] {
color_button ( gui , " c " , newC . get ( ) , {
. isSelected = newC - > x ( ) = = color - > x ( ) & & newC - > y ( ) = = color - > y ( ) & & newC - > z ( ) = = color - > z ( ) ,
. hasAlpha = false ,
. onClick = [ newC , color , onChange ] {
// We want to keep the old color's alpha
color - > x ( ) = newC - > x ( ) ;
color - > y ( ) = newC - > y ( ) ;
color - > z ( ) = newC - > z ( ) ;
if ( onChange ) onChange ( ) ;
}
} ) ;
} ) ;
}
i + + ;
if ( i % 6 = = 0 )
break ;
}
}
}
}
}
} ) ;
2026-03-27 09:51:53 +03:00
2026-04-03 09:52:45 +03:00
if ( paletteData . selectedPalette ! = 0 ) {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. padding = { . top = 3 , . bottom = 3 } ,
. childGap = io . theme - > childGap1 ,
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_CENTER } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT
}
} ) {
svg_icon_button ( gui , " addcolor " , " data/icons/plus.svg " , {
. onClick = [ & , color ] {
std : : erase ( palette , Vector3f { color - > x ( ) , color - > y ( ) , color - > z ( ) } ) ;
palette . emplace_back ( color - > x ( ) , color - > y ( ) , color - > z ( ) ) ;
}
} ) ;
svg_icon_button ( gui , " deletecolor " , " data/icons/close.svg " , {
. onClick = [ & , color ] {
std : : erase ( palette , Vector3f { color - > x ( ) , color - > y ( ) , color - > z ( ) } ) ;
}
} ) ;
}
}
2026-03-27 09:51:53 +03:00
2026-04-03 09:52:45 +03:00
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. padding = { . top = 3 , . bottom = 3 } ,
. childGap = io . theme - > childGap1 ,
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_CENTER } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT
}
} ) {
std : : vector < std : : string > paletteNames ;
2026-04-12 12:36:00 +03:00
for ( auto & p : main . conf . palettes )
2026-04-03 09:52:45 +03:00
paletteNames . emplace_back ( p . name ) ;
gui . element < DropDown < size_t > > ( " paletteselector " , & paletteData . selectedPalette , paletteNames , DropdownOptions {
. onClick = [ & ] { gui . set_to_layout ( ) ; }
} ) ;
svg_icon_button ( gui , " paletteadd " , " data/icons/plus.svg " , {
. size = 25.0f ,
. onClick = [ & ] {
paletteData . addingPalette = ! paletteData . addingPalette ;
}
} ) ;
2026-05-17 19:22:55 +03:00
if ( paletteData . selectedPalette ! = 0 ) {
svg_icon_button ( gui , " paletteremove " , " data/icons/close.svg " , {
. size = 25.0f ,
. onClick = [ & ] {
main . conf . palettes . erase ( main . conf . palettes . begin ( ) + paletteData . selectedPalette ) ;
paletteData . selectedPalette = 0 ;
}
} ) ;
}
2026-04-03 09:52:45 +03:00
}
if ( paletteData . addingPalette ) {
input_text_field ( gui , " paletteinputname " , " Name " , & paletteData . newPaletteStr ) ;
text_button_wide ( " addpalettebutton " , " Create " , [ & ] {
if ( ! paletteData . newPaletteStr . empty ( ) ) {
2026-04-12 12:36:00 +03:00
main . conf . palettes . emplace_back ( ) ;
main . conf . palettes . back ( ) . name = paletteData . newPaletteStr ;
paletteData . selectedPalette = main . conf . palettes . size ( ) - 1 ;
2026-04-03 09:52:45 +03:00
paletteData . addingPalette = false ;
}
} ) ;
}
2026-03-30 18:51:14 +03:00
} ) ;
2026-03-27 09:51:53 +03:00
}
2025-07-06 20:43:53 +03:00
void Toolbar : : player_list ( ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
2026-04-05 20:26:03 +03:00
center_obstructing_window_gui ( " player client list " , CLAY_SIZING_FIT ( 500 ) , CLAY_SIZING_FIT ( 0 ) , [ & ] {
2026-03-30 18:51:14 +03:00
gui . new_id ( " client list " , [ & ] {
text_label_centered ( gui , " Player List " ) ;
if ( ! main . world - > clientStillConnecting ) {
left_to_right_line_layout ( gui , [ & ] ( ) {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_FIXED ( 20 ) , . height = CLAY_SIZING_FIXED ( 20 ) }
} ,
. backgroundColor = convert_vec4 < Clay_Color > ( SkColor4f { main . world - > ownClientData - > get_cursor_color ( ) . x ( ) , main . world - > ownClientData - > get_cursor_color ( ) . y ( ) , main . world - > ownClientData - > get_cursor_color ( ) . z ( ) , 1.0f } ) ,
. cornerRadius = CLAY_CORNER_RADIUS ( 3 )
} ) { }
text_label ( gui , main . world - > ownClientData - > get_display_name ( ) ) ;
} ) ;
size_t num = 0 ;
for ( auto & client : main . world - > clients - > get_data ( ) ) {
if ( client ! = main . world - > ownClientData ) {
gui . new_id ( num + + , [ & ] {
left_to_right_line_layout ( gui , [ & ] ( ) {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_FIXED ( 20 ) , . height = CLAY_SIZING_FIXED ( 20 ) }
} ,
. backgroundColor = convert_vec4 < Clay_Color > ( SkColor4f { client - > get_cursor_color ( ) . x ( ) , client - > get_cursor_color ( ) . y ( ) , client - > get_cursor_color ( ) . z ( ) , 1.0f } ) ,
. cornerRadius = CLAY_CORNER_RADIUS ( 3 )
} ) { }
text_label ( gui , client - > get_display_name ( ) ) ;
CLAY_AUTO_ID ( { . layout = { . sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } } } ) { }
text_button ( gui , " teleport button " , " Jump To " , { . onClick = [ & ] {
main . world - > drawData . cam . smooth_move_to ( * main . world , client - > get_cam_coords ( ) , client - > get_window_size ( ) ) ;
} } ) ;
} ) ;
} ) ;
}
2025-12-17 14:46:27 +03:00
}
}
2026-03-30 18:51:14 +03:00
text_button ( gui , " close list " , " Done " , { . wide = true , . onClick = [ & ] {
playerMenuOpen = false ;
} } ) ;
} ) ;
2026-04-05 20:26:03 +03:00
} ) ;
2025-07-06 20:43:53 +03:00
}
2026-01-01 22:46:02 +03:00
void Toolbar : : open_world_file ( bool isClient , const std : : string & netSource , const std : : string & serverLocalID2 ) {
2025-07-06 20:43:53 +03:00
# ifdef __EMSCRIPTEN__
static struct UploadData {
2026-01-01 22:46:02 +03:00
bool iC ;
2025-07-06 20:43:53 +03:00
std : : string nS ;
std : : string sLID ;
MainProgram * main ;
} uploadData ;
2026-01-01 22:46:02 +03:00
uploadData . iC = isClient ;
2025-07-06 20:43:53 +03:00
uploadData . nS = netSource ;
uploadData . sLID = serverLocalID2 ;
uploadData . main = & main ;
emscripten_browser_file : : upload ( " . " + World : : FILE_EXTENSION , [ ] ( std : : string const & fileName , std : : string const & mimeType , std : : string_view buffer , void * callbackData ) {
if ( ! buffer . empty ( ) ) {
UploadData * uD = ( UploadData * ) callbackData ;
2026-04-12 17:31:32 +03:00
CustomEvents : : emit_event < CustomEvents : : OpenInfiniPaintFileEvent > ( {
2026-01-01 22:46:02 +03:00
. isClient = uD - > iC ,
2025-11-21 08:35:47 +03:00
. filePathSource = std : : filesystem : : path ( fileName ) ,
2025-07-06 20:43:53 +03:00
. netSource = uD - > nS ,
. serverLocalID = uD - > sLID ,
. fileDataBuffer = buffer
2026-04-12 17:31:32 +03:00
} ) ;
2025-07-06 20:43:53 +03:00
}
} , & uploadData ) ;
# else
2026-01-14 07:33:54 +03:00
open_file_selector ( " Open " , { { " InfiniPaint Canvas " , World : : FILE_EXTENSION } , { " Any File " , " * " } } , [ & , isClient = isClient , netSource = netSource , serverLocalID2 = serverLocalID2 ] ( const std : : filesystem : : path & p , const auto & e ) {
2026-04-11 16:36:24 +03:00
CustomEvents : : emit_event < CustomEvents : : OpenInfiniPaintFileEvent > ( {
2026-01-01 22:46:02 +03:00
. isClient = isClient ,
2025-11-21 08:35:47 +03:00
. filePathSource = p ,
2025-07-06 20:43:53 +03:00
. netSource = netSource ,
. serverLocalID = serverLocalID2
} ) ;
} ) ;
# endif
}
2026-03-27 09:51:53 +03:00
void Toolbar : : center_obstructing_window_gui ( const char * id , Clay_SizingAxis x , Clay_SizingAxis y , const std : : function < void ( ) > & innerContent ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
auto & io = gui . io ;
2026-04-02 18:23:36 +03:00
gui . set_z_index ( 100 , [ & ] {
2026-04-02 21:57:26 +03:00
gui . element < LayoutElement > ( id , [ & ] ( LayoutElement * , const Clay_ElementId & id ) {
2026-04-02 18:23:36 +03:00
CLAY ( id , {
. layout = {
. sizing = { . width = x , . height = y } ,
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
. childGap = io . theme - > childGap1 ,
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
. backgroundColor = convert_vec4 < Clay_Color > ( io . theme - > backColor1 ) ,
. cornerRadius = CLAY_CORNER_RADIUS ( io . theme - > windowCorners1 ) ,
. floating = { . zIndex = gui . get_z_index ( ) , . attachPoints = { . element = CLAY_ATTACH_POINT_CENTER_CENTER , . parent = CLAY_ATTACH_POINT_CENTER_CENTER } , . attachTo = CLAY_ATTACH_TO_PARENT }
} ) {
innerContent ( ) ;
}
} ) ;
2026-03-27 09:51:53 +03:00
} ) ;
}
void Toolbar : : text_button_wide ( const char * id , const char * str , const std : : function < void ( ) > & onClick ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
2026-03-27 09:51:53 +03:00
text_button ( gui , id , str , {
. wide = true ,
. onClick = onClick
} ) ;
}
2025-07-06 20:43:53 +03:00
void Toolbar : : options_menu ( ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
2025-07-06 20:43:53 +03:00
switch ( optionsMenuType ) {
2026-03-27 09:51:53 +03:00
case HOST_MENU : {
center_obstructing_window_gui ( " host menu " , CLAY_SIZING_FIT ( 650 ) , CLAY_SIZING_FIT ( 0 ) , [ & ] {
2026-05-13 19:56:14 +03:00
input_text_field ( gui , " lobby " , " Lobby " , & serverToConnectTo , {
. immutable = true
} ) ;
2026-03-27 09:51:53 +03:00
left_to_right_line_layout ( gui , [ & ] ( ) {
text_button_wide ( " copy lobby address " , " Copy Lobby Address " , [ & ] {
main . input . set_clipboard_str ( serverToConnectTo ) ;
} ) ;
text_button_wide ( " host file " , " Host " , [ & ] {
main . world - > start_hosting ( serverToConnectTo , serverLocalID ) ;
optionsMenuOpen = false ;
} ) ;
text_button_wide ( " cancel " , " Cancel " , [ & ] {
optionsMenuOpen = false ;
} ) ;
} ) ;
} ) ;
break ;
}
2025-07-06 20:43:53 +03:00
case CONNECT_MENU : {
2026-03-27 09:51:53 +03:00
center_obstructing_window_gui ( " connect menu " , CLAY_SIZING_FIT ( 650 ) , CLAY_SIZING_FIT ( 0 ) , [ & ] {
input_text_field ( gui , " lobby " , " Lobby " , & serverToConnectTo ) ;
left_to_right_line_layout ( gui , [ & ] ( ) {
text_button_wide ( " connect " , " Connect " , [ & ] {
if ( serverToConnectTo . length ( ) ! = ( NetLibrary : : LOCALID_LEN + NetLibrary : : GLOBALID_LEN ) )
2026-05-16 21:56:58 +03:00
Logger : : get ( ) . log ( Logger : : LogType : : DESKTOP_USERINFO , " Connect issue: Incorrect address length " ) ;
2026-03-27 09:51:53 +03:00
else if ( serverToConnectTo . substr ( 0 , NetLibrary : : GLOBALID_LEN ) = = NetLibrary : : get_global_id ( ) )
2026-05-16 21:56:58 +03:00
Logger : : get ( ) . log ( Logger : : LogType : : DESKTOP_USERINFO , " Connect issue: Can't connect to your own address " ) ;
2026-03-27 09:51:53 +03:00
else {
2026-04-11 16:36:24 +03:00
CustomEvents : : emit_event < CustomEvents : : OpenInfiniPaintFileEvent > ( {
2026-03-27 09:51:53 +03:00
. isClient = true ,
. netSource = serverToConnectTo
2026-04-05 20:26:03 +03:00
} ) ;
2025-07-06 20:43:53 +03:00
optionsMenuOpen = false ;
}
} ) ;
2026-03-27 09:51:53 +03:00
text_button_wide ( " cancel " , " Cancel " , [ & ] {
optionsMenuOpen = false ;
2025-07-06 20:43:53 +03:00
} ) ;
2026-03-27 09:51:53 +03:00
} ) ;
} ) ;
2025-07-06 20:43:53 +03:00
break ;
}
case GENERAL_SETTINGS_MENU : {
2026-03-27 09:51:53 +03:00
center_obstructing_window_gui ( " gsettings " , CLAY_SIZING_FIXED ( 600 ) , CLAY_SIZING_FIXED ( 500 ) , [ & ] {
general_settings_inner_gui ( ) ;
} ) ;
2025-07-06 20:43:53 +03:00
break ;
}
case LOBBY_INFO_MENU : {
2026-03-27 09:51:53 +03:00
center_obstructing_window_gui ( " lobby info menu " , CLAY_SIZING_FIT ( 650 ) , CLAY_SIZING_FIT ( 0 ) , [ & ] {
input_text_field ( gui , " lobby " , " Lobby " , & main . world - > netSource ) ;
left_to_right_line_layout ( gui , [ & ] ( ) {
text_button_wide ( " copy lobby address " , " Copy Lobby Address " , [ & ] {
main . input . set_clipboard_str ( main . world - > netSource ) ;
2025-07-06 20:43:53 +03:00
} ) ;
2026-03-27 09:51:53 +03:00
text_button_wide ( " done " , " Done " , [ & ] {
optionsMenuOpen = false ;
} ) ;
} ) ;
} ) ;
2025-07-06 20:43:53 +03:00
break ;
}
2025-08-01 10:58:45 +03:00
case CANVAS_SETTINGS_MENU : {
2026-03-27 09:51:53 +03:00
center_obstructing_window_gui ( " canvas settings menu " , CLAY_SIZING_FIT ( 500 ) , CLAY_SIZING_FIT ( 0 ) , [ & ] {
auto newColorToSet = std : : make_shared < SkColor4f > ( main . world - > canvasTheme . get_back_color ( ) ) ;
color_picker_button_field ( gui , " canvasColor " , " Canvas Color " , newColorToSet . get ( ) , {
. hasAlpha = false ,
. onEdit = [ & , newColorToSet ] {
2026-03-30 17:05:24 +03:00
main . world - > canvasTheme . set_back_color ( convert_vec3 < Vector3f > ( * newColorToSet ) ) ;
2026-03-27 09:51:53 +03:00
}
} ) ;
text_button_wide ( " done " , " Done " , [ & ] {
2025-08-01 10:58:45 +03:00
optionsMenuOpen = false ;
2026-03-27 09:51:53 +03:00
} ) ;
} ) ;
2025-08-01 10:58:45 +03:00
break ;
}
2025-07-06 20:43:53 +03:00
case SET_DOWNLOAD_NAME : {
2026-03-27 09:51:53 +03:00
center_obstructing_window_gui ( " set download name menu " , CLAY_SIZING_FIT ( 500 ) , CLAY_SIZING_FIT ( 0 ) , [ & ] {
input_text_field ( gui , " file name " , " File Name " , & downloadNameSet ) ;
left_to_right_line_layout ( gui , [ & ] ( ) {
text_button_wide ( " download save button " , " Save " , [ & ] {
2025-07-28 18:21:09 +03:00
main . world - > save_to_file ( downloadNameSet ) ;
2025-07-06 20:43:53 +03:00
optionsMenuOpen = false ;
2026-03-27 09:51:53 +03:00
} ) ;
text_button_wide ( " cancel " , " Cancel " , [ & ] {
2025-07-06 20:43:53 +03:00
optionsMenuOpen = false ;
2026-03-27 09:51:53 +03:00
} ) ;
2025-07-06 20:43:53 +03:00
} ) ;
2026-03-27 09:51:53 +03:00
} ) ;
2025-07-06 20:43:53 +03:00
break ;
}
case ABOUT_MENU : {
2026-03-31 21:21:49 +03:00
center_obstructing_window_gui ( " about menu " , CLAY_SIZING_FIXED ( 650 ) , CLAY_SIZING_FIXED ( 500 ) , [ & ] {
2026-03-27 09:51:53 +03:00
about_menu_inner_gui ( ) ;
} ) ;
2025-07-06 20:43:53 +03:00
break ;
}
}
}
2026-03-27 09:51:53 +03:00
void Toolbar : : general_settings_inner_gui ( ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
auto & io = gui . io ;
2026-03-27 09:51:53 +03:00
CLAY_AUTO_ID ( {
2025-07-06 20:43:53 +03:00
. layout = {
2026-03-27 09:51:53 +03:00
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
2026-03-31 13:17:58 +03:00
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
. childGap = io . theme - > childGap1 ,
2026-03-27 09:51:53 +03:00
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT
}
2025-07-06 20:43:53 +03:00
} ) {
2026-03-27 09:51:53 +03:00
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_FIT ( 150 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
2026-03-31 13:17:58 +03:00
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
2026-03-27 09:51:53 +03:00
. childGap = 2 ,
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
}
} ) {
auto category_button = [ & ] ( const char * id , const char * str , GeneralSettingsOptions opt ) {
text_button ( gui , id , str , {
. drawType = SelectableButton : : DrawType : : TRANSPARENT_ALL ,
. isSelected = generalSettingsOptions = = opt ,
. wide = true ,
2026-03-31 20:52:46 +03:00
. centered = false ,
. onClick = [ & , opt ] {
2026-04-12 12:36:00 +03:00
main . g . load_theme ( main . conf . configPath , main . conf . themeCurrentlyLoaded ) ;
2026-03-27 09:51:53 +03:00
themeData . selectedThemeIndex = std : : nullopt ;
generalSettingsOptions = opt ;
2026-04-02 19:18:39 +03:00
main . keybindWaiting = std : : nullopt ;
2026-03-27 09:51:53 +03:00
}
} ) ;
} ;
category_button ( " Generalbutton " , " General " , GSETTINGS_GENERAL ) ;
category_button ( " Tabletbutton " , " Tablet " , GSETTINGS_TABLET ) ;
category_button ( " Themebutton " , " Theme " , GSETTINGS_THEME ) ;
category_button ( " Keybindsbutton " , " Keybinds " , GSETTINGS_KEYBINDS ) ;
category_button ( " Debugbutton " , " Debug " , GSETTINGS_DEBUG ) ;
}
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
}
} ) {
2026-04-01 10:18:38 +03:00
auto general_scroll_area = [ & ] ( const char * id , const std : : function < void ( ) > & innerContent ) {
2026-04-01 21:49:44 +03:00
gui . clipping_element < ScrollArea > ( " general settings scroll area " , ScrollArea : : Options {
2026-04-01 10:18:38 +03:00
. scrollVertical = true ,
. clipVertical = true ,
2026-04-29 15:14:13 +03:00
. scrollbarY = ScrollArea : : ScrollbarType : : NORMAL ,
2026-04-01 10:18:38 +03:00
. innerContent = [ & , innerContent ] ( const ScrollArea : : InnerContentParameters & ) {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
. childGap = io . theme - > childGap1 ,
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
}
} ) {
innerContent ( ) ;
2026-03-27 09:51:53 +03:00
}
2026-04-01 10:18:38 +03:00
}
} ) ;
} ;
switch ( generalSettingsOptions ) {
case GSETTINGS_GENERAL : {
general_scroll_area ( " general settings " , [ & ] {
2026-05-14 11:53:36 +03:00
input_text_field ( gui , " display name input " , " Display name " , & main . conf . displayName , {
. onEdit = [ & ] {
main . update_display_names ( ) ;
}
} ) ;
2026-04-01 10:18:38 +03:00
color_picker_button_field ( gui , " defaultCanvasBackgroundColor " , " Default canvas background color " , & main . conf . defaultCanvasBackgroundColor , { . hasAlpha = false } ) ;
# ifndef __EMSCRIPTEN__
checkbox_boolean_field ( gui , " native file pick " , " Use native file picker " , & main . conf . useNativeFilePicker ) ;
checkbox_boolean_field ( gui , " update notifications enable " , " Check for updates on startup " , & main . conf . checkForUpdates ) ;
# endif
slider_scalar_field ( gui , " drag zoom slider " , " Drag zoom speed " , & main . conf . dragZoomSpeed , 0.0 , 1.0 , { . decimalPrecision = 3 } ) ;
slider_scalar_field ( gui , " scroll zoom slider " , " Scroll zoom speed " , & main . conf . scrollZoomSpeed , 0.0 , 1.0 , { . decimalPrecision = 3 } ) ;
checkbox_boolean_field ( gui , " flip zoom tool direction " , " Flip zoom tool direction " , & main . conf . flipZoomToolDirection ) ;
checkbox_boolean_field ( gui , " make all tools share same size " , " Make all tools share size " , & main . toolConfig . globalConf . useGlobalRelativeWidth ) ;
# ifndef __EMSCRIPTEN__
checkbox_boolean_field ( gui , " disable graphics driver workarounds " , " Disable graphics driver workarounds (enabling or disabling this might fix some graphical glitches, requires restart) " , & main . conf . disableGraphicsDriverWorkarounds ) ;
# endif
input_scalar_field ( gui , " jump transition time " , " Jump transition time " , & main . conf . jumpTransitionTime , 0.01f , 1000.0f , { . decimalPrecision = 2 } ) ;
2026-04-07 20:21:41 +03:00
input_scalar_field ( gui , " Max GUI Scale " , " Max GUI Scale " , & main . conf . guiScale , 0.5f , 5.0f , {
. decimalPrecision = 1 ,
. onEdit = [ & ] { main . g . window_update ( ) ; }
} ) ;
2026-04-01 10:18:38 +03:00
text_label ( gui , " Anti-aliasing: " ) ;
radio_button_selector ( gui , " Antialiasing selector " , & main . conf . antialiasing , {
{ " None " , GlobalConfig : : AntiAliasing : : NONE } ,
{ " Skia " , GlobalConfig : : AntiAliasing : : SKIA } ,
{ " Dynamic MSAA " , GlobalConfig : : AntiAliasing : : DYNAMIC_MSAA }
} , [ & ] {
main . refresh_draw_surfaces ( ) ;
} ) ;
text_label ( gui , " VSync: " ) ;
radio_button_selector ( gui , " VSync selector " , & main . conf . vsyncValue , {
{ " On " , 1 } ,
{ " Off " , 0 } ,
{ " Adaptive " , - 1 }
} , [ & ] {
main . set_vsync_value ( main . conf . vsyncValue ) ;
} ) ;
2026-03-27 09:51:53 +03:00
2026-04-01 10:18:38 +03:00
# ifndef __EMSCRIPTEN__
checkbox_boolean_field ( gui , " apply display scale " , " Apply display scale " , & main . conf . applyDisplayScale ) ;
# endif
} ) ;
break ;
}
case GSETTINGS_TABLET : {
general_scroll_area ( " tablet settings " , [ & ] {
checkbox_boolean_field ( gui , " pen pressure width " , " Pen pressure affects brush size " , & main . conf . tabletOptions . pressureAffectsBrushWidth ) ;
input_scalar_field < uint8_t > ( gui , " middle click " , " Middle click pen button " , & main . conf . tabletOptions . middleClickButton , 1 , 255 ) ;
input_scalar_field < uint8_t > ( gui , " right click " , " Right click pen button " , & main . conf . tabletOptions . rightClickButton , 1 , 255 ) ;
slider_scalar_field ( gui , " tablet brush minimum size " , " Brush relative minimum size " , & main . conf . tabletOptions . brushMinimumSize , 0.0f , 1.0f , { . decimalPrecision = 3 } ) ;
2026-05-16 20:40:23 +03:00
slider_scalar_field ( gui , " tablet brush pressure smoothing factor " , " Brush pressure smoothing factor " , & main . conf . tabletOptions . brushPressureSmoothingFactor , 0.0f , 1.0f , { . decimalPrecision = 3 } ) ;
2026-04-01 10:18:38 +03:00
checkbox_boolean_field ( gui , " tablet zoom with button method " , " Zoom when pen touching tablet and pen button assigned to middle click is held " , & main . conf . tabletOptions . zoomWhilePenDownAndButtonHeld ) ;
# ifdef _WIN32
2026-04-11 11:17:26 +03:00
checkbox_boolean_field ( gui , " mouse ignore when pen proximity " , " Ignore mouse movement when pen in proximity " , & main . conf . tabletOptions . ignoreMouseMovementWhenPenInProximity ) ;
2026-04-01 10:18:38 +03:00
# endif
2026-05-15 09:51:25 +03:00
checkbox_boolean_field ( gui , " disable touch when pen in proximity " , " Disable touch when pen in proximity " , & main . conf . tabletOptions . disableTouchWhenPenInProximity ) ;
2026-04-01 10:18:38 +03:00
} ) ;
break ;
}
case GSETTINGS_THEME : {
general_scroll_area ( " theme " , [ & ] {
if ( ! themeData . selectedThemeIndex )
reload_theme_list ( ) ;
2026-05-12 21:13:31 +03:00
CLAY_AUTO_ID ( { . layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) } ,
. childGap = io . theme - > childGap1 ,
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_CENTER } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT ,
2026-04-01 10:18:38 +03:00
}
} ) {
text_label ( gui , " Theme: " ) ;
gui . element < DropDown < size_t > > ( " dropdownSelectThemes " , & themeData . selectedThemeIndex . value ( ) , themeData . themeDirList , DropdownOptions {
. onClick = [ & ] ( ) {
main . conf . themeCurrentlyLoaded = themeData . themeDirList [ themeData . selectedThemeIndex . value ( ) ] ;
reload_theme_list ( ) ;
}
} ) ;
}
left_to_right_line_layout ( gui , [ & ] ( ) {
if ( themeData . selectedThemeIndex ! = 0 ) {
text_button_wide ( " savethemebutton " , " Save " , [ & ] {
main . conf . themeCurrentlyLoaded = themeData . themeDirList [ themeData . selectedThemeIndex . value ( ) ] ;
2026-04-12 12:36:00 +03:00
main . g . save_theme ( main . conf . configPath , main . conf . themeCurrentlyLoaded ) ;
2026-04-01 10:18:38 +03:00
reload_theme_list ( ) ;
2026-03-30 18:51:14 +03:00
} ) ;
2026-03-27 09:51:53 +03:00
}
2026-04-01 10:18:38 +03:00
text_button_wide ( " saveasthemebutton " , " Save As " , [ & ] ( ) {
themeData . openedSaveAsMenu = ! themeData . openedSaveAsMenu ;
} ) ;
text_button_wide ( " reloadthemebutton " , " Reload " , [ & ] {
main . conf . themeCurrentlyLoaded = themeData . themeDirList [ themeData . selectedThemeIndex . value ( ) ] ;
reload_theme_list ( ) ;
} ) ;
if ( themeData . selectedThemeIndex ! = 0 ) {
text_button_wide ( " deletethemebutton " , " Delete " , [ & ] {
2026-04-12 12:36:00 +03:00
try { std : : filesystem : : remove ( main . conf . configPath / " themes " / ( themeData . themeDirList [ themeData . selectedThemeIndex . value ( ) ] + " .json " ) ) ; } catch ( . . . ) { }
2026-04-01 10:18:38 +03:00
main . conf . themeCurrentlyLoaded = " Default " ;
reload_theme_list ( ) ;
2026-03-30 18:51:14 +03:00
} ) ;
2026-03-27 09:51:53 +03:00
}
2026-04-01 10:18:38 +03:00
} ) ;
if ( themeData . openedSaveAsMenu ) {
input_text_field ( gui , " Theme name: " , " Theme name: " , & main . conf . themeCurrentlyLoaded ) ;
left_to_right_line_layout ( gui , [ & ] ( ) {
text_button_wide ( " saveasdone " , " Done " , [ & ] {
2026-04-12 12:36:00 +03:00
main . g . save_theme ( main . conf . configPath , main . conf . themeCurrentlyLoaded ) ;
2026-04-01 10:18:38 +03:00
reload_theme_list ( ) ;
} ) ;
text_button_wide ( " saveascancel " , " Cancel " , [ & ] {
themeData . openedSaveAsMenu = false ;
} ) ;
} ) ;
}
text_label ( gui , " Edit theme: " ) ;
text_label_light ( gui , " Note: Changes only remain if theme is saved " ) ;
auto theme_color_field = [ & ] ( const char * id , const char * name , SkColor4f * c ) {
color_picker_button_field < SkColor4f > ( gui , id , name , c , { } ) ;
} ;
theme_color_field ( " fillColor2 " , " Fill Color 2 " , & io . theme - > fillColor2 ) ;
theme_color_field ( " backColor1 " , " Back Color 1 " , & io . theme - > backColor1 ) ;
theme_color_field ( " backColor2 " , " Back Color 2 " , & io . theme - > backColor2 ) ;
theme_color_field ( " frontColor1 " , " Front Color 1 " , & io . theme - > frontColor1 ) ;
theme_color_field ( " frontColor2 " , " Front Color 2 " , & io . theme - > frontColor2 ) ;
theme_color_field ( " warningColor " , " Warning Color " , & io . theme - > warningColor ) ;
theme_color_field ( " errorColor " , " Error Color " , & io . theme - > errorColor ) ;
//gui.slider_scalar_field("hoverExpandTime", "Hover Expand Time", &io.theme->hoverExpandTime, 0.001f, 1.0f);
input_scalar_field < uint16_t > ( gui , " childGap1 " , " Gap between child elements " , & io . theme - > childGap1 , 0 , 30 ) ;
input_scalar_field < uint16_t > ( gui , " padding1 " , " Window padding " , & io . theme - > padding1 , 0 , 30 ) ;
slider_scalar_field < float > ( gui , " windowCorners1 " , " Window corner radius " , & io . theme - > windowCorners1 , 0 , 30 ) ;
} ) ;
break ;
}
case GSETTINGS_KEYBINDS : {
general_scroll_area ( " keybind entries " , [ & ] {
for ( unsigned i = 0 ; i < InputManager : : KEY_ASSIGNABLE_COUNT ; i + + ) {
gui . new_id ( i , [ & ] {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. padding = CLAY_PADDING_ALL ( 0 ) ,
. childGap = io . theme - > childGap1 ,
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_CENTER } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT
2026-03-30 18:51:14 +03:00
}
2026-04-01 10:18:38 +03:00
} ) {
text_label ( gui , std : : string ( nlohmann : : json ( static_cast < InputManager : : KeyCodeEnum > ( i ) ) ) ) ;
auto f = std : : find_if ( main . input . keyAssignments . begin ( ) , main . input . keyAssignments . end ( ) , [ & ] ( auto & p ) {
return p . second = = i ;
} ) ;
std : : string assignedKeystrokeStr = f ! = main . input . keyAssignments . end ( ) ? main . input . key_assignment_to_str ( f - > first ) : " " ;
text_button ( gui , " keybind button " , assignedKeystrokeStr , {
2026-04-02 19:18:39 +03:00
. isSelected = main . keybindWaiting . has_value ( ) & & main . keybindWaiting . value ( ) = = i ,
2026-04-01 10:18:38 +03:00
. wide = true ,
2026-04-02 19:18:39 +03:00
. onClick = [ & , i ] {
main . keybindWaiting = i ;
2026-03-30 18:51:14 +03:00
}
2026-03-27 09:51:53 +03:00
} ) ;
}
2026-04-01 10:18:38 +03:00
} ) ;
2026-03-27 09:51:53 +03:00
}
2026-04-01 10:18:38 +03:00
} ) ;
break ;
2025-07-06 20:43:53 +03:00
}
2026-04-01 10:18:38 +03:00
case GSETTINGS_DEBUG : {
general_scroll_area ( " debug settings menu " , [ & ] {
2026-05-16 22:14:02 +03:00
# ifndef __EMSCRIPTEN__
checkbox_boolean_field ( gui , " use mobile UI " , " Use mobile UI (requires restart) " , & main . conf . mobileUI ) ;
# endif
2026-04-01 10:18:38 +03:00
input_scalars_field ( gui , " jump transition easing " , " Jump easing " , & main . conf . jumpTransitionEasing , 4 , - 10.0f , 10.0f , { . decimalPrecision = 2 } ) ;
input_scalar_field < int > ( gui , " image load max threads " , " Maximum image loading threads " , & ImageResourceDisplay : : IMAGE_LOAD_THREAD_COUNT_MAX , 1 , 10000 ) ;
text_label_light ( gui , " Cache related settings " ) ;
input_scalar_field < size_t > ( gui , " cache node resolution " , " Cache node resolution " , & DrawingProgramCache : : CACHE_NODE_RESOLUTION , 256 , 8192 ) ;
input_scalar_field < size_t > ( gui , " max cache nodes " , " Maximum cached nodes " , & DrawingProgramCache : : MAXIMUM_DRAW_CACHE_SURFACES , 2 , 10000 ) ;
size_t cacheVRAMConsumptionInMB = ( DrawingProgramCache : : MAXIMUM_DRAW_CACHE_SURFACES // Number of surfaces
* DrawingProgramCache : : CACHE_NODE_RESOLUTION * DrawingProgramCache : : CACHE_NODE_RESOLUTION // Number of pixels per cache surface
* 4 ) // 4 Channels per pixel (RGBA)
/ ( 1024 * 1024 ) ; // Bytes -> Megabytes conversion
text_label_light ( gui , " Cache max VRAM consumption (MB): " + std : : to_string ( cacheVRAMConsumptionInMB ) ) ;
input_scalar_field < size_t > ( gui , " max components in node " , " Maximum components in single node " , & DrawingProgramCache : : MAXIMUM_COMPONENTS_IN_SINGLE_NODE , 2 , 10000 ) ;
input_scalar_field < size_t > ( gui , " components to force cache rebuild " , " Number of components to force cache rebuild " , & DrawingProgramCache : : MINIMUM_COMPONENTS_TO_START_REBUILD , 1 , 1000000 ) ;
input_scalar_field < size_t > ( gui , " maximum frame time to force cache rebuild " , " Maximum frame time to force cache rebuild (ms) " , & DrawingProgramCache : : MILLISECOND_FRAME_TIME_TO_FORCE_CACHE_REFRESH , 1 , 1000000 ) ;
input_scalar_field < size_t > ( gui , " minimum time to force cache rebuild " , " Minimum time to check cache rebuild (ms) " , & DrawingProgramCache : : MILLISECOND_MINIMUM_TIME_TO_CHECK_FORCE_REFRESH , 1 , 1000000 ) ;
} ) ;
break ;
}
}
2026-03-27 09:51:53 +03:00
text_button_wide ( " done menu " , " Done " , [ & ] {
main . save_config ( ) ;
2026-04-12 12:36:00 +03:00
main . g . load_theme ( main . conf . configPath , main . conf . themeCurrentlyLoaded ) ;
2026-03-27 09:51:53 +03:00
themeData . selectedThemeIndex = std : : nullopt ;
2026-04-02 19:18:39 +03:00
main . keybindWaiting = std : : nullopt ;
2026-03-27 09:51:53 +03:00
optionsMenuOpen = false ;
} ) ;
}
}
}
void Toolbar : : about_menu_inner_gui ( ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
auto & io = gui . io ;
2026-03-27 09:51:53 +03:00
left_to_right_line_layout ( gui , [ & ] ( ) {
2026-03-30 18:51:14 +03:00
gui . new_id ( " Menu Selector " , [ & ] {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_FIT ( 200 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
2026-03-31 13:17:58 +03:00
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
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-04-01 21:49:44 +03:00
gui . clipping_element < ScrollArea > ( " About Menu Selector Scroll Area " , ScrollArea : : Options {
2026-03-31 21:21:49 +03:00
. scrollVertical = true ,
. clipVertical = true ,
2026-04-29 15:14:13 +03:00
. scrollbarY = ScrollArea : : ScrollbarType : : NORMAL ,
2026-03-30 18:51:14 +03:00
. innerContent = [ & ] ( const ScrollArea : : InnerContentParameters & ) {
2025-12-02 16:09:30 +03:00
CLAY_AUTO_ID ( {
2025-07-06 20:43:53 +03:00
. layout = {
2026-03-30 18:51:14 +03:00
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) }
2025-07-06 20:43:53 +03:00
}
} ) {
2026-03-30 18:51:14 +03:00
text_button ( gui , " infinipaintnoticebutton " , " InfiniPaint " , {
2026-03-27 09:51:53 +03:00
. drawType = SelectableButton : : DrawType : : TRANSPARENT_ALL ,
2026-03-30 18:51:14 +03:00
. isSelected = selectedLicense = = - 1 ,
2026-03-27 09:51:53 +03:00
. wide = true ,
2026-03-31 21:21:49 +03:00
. centered = false ,
2026-03-30 18:51:14 +03:00
. onClick = [ & ] { selectedLicense = - 1 ; }
2026-03-27 09:51:53 +03:00
} ) ;
2025-07-06 20:43:53 +03:00
}
2026-03-30 18:51:14 +03:00
text_label_light_centered ( gui , " Third Party Components " ) ;
2026-04-12 12:36:00 +03:00
for ( int i = 0 ; i < static_cast < int > ( main . conf . thirdPartyLicenses . size ( ) ) ; i + + ) {
2026-03-30 18:51:14 +03:00
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. padding = CLAY_PADDING_ALL ( 1 )
}
} ) {
gui . new_id ( i , [ & ] {
2026-04-12 12:36:00 +03:00
text_button ( gui , " noticebutton " , main . conf . thirdPartyLicenses [ i ] . first , {
2026-03-30 18:51:14 +03:00
. drawType = SelectableButton : : DrawType : : TRANSPARENT_ALL ,
. isSelected = selectedLicense = = i ,
. wide = true ,
2026-03-31 21:21:49 +03:00
. centered = false ,
2026-03-30 18:51:14 +03:00
. onClick = [ & , i ] { selectedLicense = i ; }
} ) ;
} ) ;
}
}
2025-07-06 20:43:53 +03:00
}
2026-03-30 18:51:14 +03:00
} ) ;
}
} ) ;
2026-03-27 09:51:53 +03:00
2026-04-01 21:49:44 +03:00
gui . clipping_element < ScrollArea > ( " About Menu Text Scroll Area " , ScrollArea : : Options {
2026-03-27 09:51:53 +03:00
. scrollVertical = true ,
. clipVertical = true ,
2026-04-29 15:14:13 +03:00
. scrollbarY = ScrollArea : : ScrollbarType : : NORMAL ,
2026-03-27 09:51:53 +03:00
. innerContent = [ & ] ( const ScrollArea : : InnerContentParameters & ) {
2026-04-12 12:36:00 +03:00
text_label_size ( gui , ( selectedLicense = = - 1 ) ? main . conf . ownLicenseText : main . conf . thirdPartyLicenses [ selectedLicense ] . second , 0.8f ) ;
2026-03-27 09:51:53 +03:00
}
} ) ;
} ) ;
text_button_wide ( " done " , " Done " , [ & ] {
optionsMenuOpen = false ;
} ) ;
2025-07-06 20:43:53 +03:00
}
void Toolbar : : reload_theme_list ( ) {
themeData . selectedThemeIndex = 0 ;
themeData . themeDirList . clear ( ) ;
themeData . themeDirList . emplace_back ( " Default " ) ;
2026-04-12 12:36:00 +03:00
std : : filesystem : : path themeDir = main . conf . configPath / " themes " ;
2025-07-06 20:43:53 +03:00
if ( std : : filesystem : : exists ( themeDir ) & & std : : filesystem : : is_directory ( themeDir ) ) {
for ( auto & theme : std : : filesystem : : recursive_directory_iterator ( themeDir ) ) {
std : : string name = theme . path ( ) . stem ( ) . string ( ) ;
if ( name ! = " Default " ) {
themeData . themeDirList . emplace_back ( name ) ;
2026-03-31 13:17:58 +03:00
if ( name = = main . conf . themeCurrentlyLoaded )
2025-07-06 20:43:53 +03:00
themeData . selectedThemeIndex = themeData . themeDirList . size ( ) - 1 ;
}
}
}
2026-04-12 12:36:00 +03:00
if ( ! main . g . load_theme ( main . conf . configPath , main . conf . themeCurrentlyLoaded ) )
2025-07-06 20:43:53 +03:00
themeData . selectedThemeIndex = 0 ;
themeData . openedSaveAsMenu = false ;
}
2026-03-27 09:51:53 +03:00
void Toolbar : : file_picker_gui_refresh_entries ( ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
2026-03-27 09:51:53 +03:00
filePicker . entries . clear ( ) ;
for ( ; ; ) {
try {
2026-04-12 12:36:00 +03:00
for ( const std : : filesystem : : path & entry : std : : filesystem : : directory_iterator ( main . conf . currentSearchPath ) )
2026-03-27 09:51:53 +03:00
filePicker . entries . emplace_back ( entry ) ;
break ;
}
catch ( const std : : exception & e ) {
2026-05-16 21:56:58 +03:00
Logger : : get ( ) . log ( Logger : : LogType : : INFO , e . what ( ) ) ;
2026-04-12 12:36:00 +03:00
if ( main . conf . currentSearchPath = = main . homePath ) // The home path must exist. If we get errors on the home path, we have a real problem
2026-03-27 09:51:53 +03:00
throw e ;
2026-04-12 12:36:00 +03:00
main . conf . currentSearchPath = main . homePath ;
2026-03-27 09:51:53 +03:00
}
}
std : : vector < std : : string > extensionList = split_string_by_token ( filePicker . extensionFilters [ filePicker . extensionSelected ] , " ; " ) ;
for ( std : : string & s : extensionList ) {
std : : transform ( s . begin ( ) , s . end ( ) , s . begin ( ) , : : tolower ) ;
s . insert ( 0 , " . " ) ;
}
std : : erase_if ( filePicker . entries , [ & ] ( const std : : filesystem : : path & a ) {
if ( extensionList . empty ( ) )
return false ;
if ( extensionList [ 0 ] = = " .* " )
return false ;
std : : string fExtension ;
if ( a . has_extension ( ) ) {
fExtension = a . extension ( ) . string ( ) ;
std : : transform ( fExtension . begin ( ) , fExtension . end ( ) , fExtension . begin ( ) , : : tolower ) ;
}
return std : : filesystem : : is_regular_file ( a ) & & ( fExtension . empty ( ) | | ! std : : ranges : : contains ( extensionList , fExtension ) ) ;
} ) ;
std : : sort ( filePicker . entries . begin ( ) , filePicker . entries . end ( ) , [ & ] ( const std : : filesystem : : path & a , const std : : filesystem : : path & b ) {
bool aDir = std : : filesystem : : is_directory ( a ) ;
bool bDir = std : : filesystem : : is_directory ( b ) ;
const std : : string & aStr = a . string ( ) ;
const std : : string & bStr = b . string ( ) ;
if ( aDir & & ! bDir )
return true ;
if ( ! aDir & & bDir )
return false ;
return std : : lexicographical_compare ( aStr . begin ( ) , aStr . end ( ) , bStr . begin ( ) , bStr . end ( ) ) ;
} ) ;
2026-04-01 22:28:48 +03:00
if ( filePicker . entriesScrollArea )
filePicker . entriesScrollArea - > reset_scroll ( ) ;
2026-03-27 09:51:53 +03:00
gui . set_to_layout ( ) ;
}
void Toolbar : : file_picker_gui_done ( ) {
if ( ! filePicker . fileName . empty ( ) ) {
2026-04-12 12:36:00 +03:00
std : : filesystem : : path pathToRet = main . conf . currentSearchPath / filePicker . fileName ;
2026-04-01 21:49:44 +03:00
if ( filePicker . isSaving )
pathToRet = force_extension_on_path ( pathToRet , filePicker . extensionFiltersComplete [ filePicker . extensionSelected ] . extensions ) ;
filePicker . postSelectionFunc ( pathToRet , filePicker . extensionFiltersComplete [ filePicker . extensionSelected ] ) ;
2026-03-27 09:51:53 +03:00
}
2026-04-01 22:28:48 +03:00
filePicker . entriesScrollArea = nullptr ;
2026-03-27 09:51:53 +03:00
filePicker . isOpen = false ;
}
2025-07-06 20:43:53 +03:00
void Toolbar : : file_picker_gui ( ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
auto & io = gui . io ;
2026-03-27 09:51:53 +03:00
center_obstructing_window_gui ( " file picker gui window " , CLAY_SIZING_FIXED ( 700 ) , CLAY_SIZING_FIXED ( 500 ) , [ & ] {
text_label_centered ( gui , filePicker . filePickerWindowName ) ;
left_to_right_line_layout ( gui , [ & ] ( ) {
svg_icon_button ( gui , " file picker back button " , " data/icons/backarrow.svg " , {
. onClick = [ & ] {
2026-04-12 12:36:00 +03:00
main . conf . currentSearchPath = main . conf . currentSearchPath . parent_path ( ) ;
2026-03-27 09:51:53 +03:00
file_picker_gui_refresh_entries ( ) ;
}
} ) ;
2026-04-12 12:36:00 +03:00
input_path_field ( gui , " file picker path " , " Path " , & main . conf . currentSearchPath , {
2026-04-09 16:30:28 +03:00
. fileTypeRestriction = std : : filesystem : : file_type : : directory ,
2026-03-27 09:51:53 +03:00
. onEdit = [ & ] {
file_picker_gui_refresh_entries ( ) ;
}
} ) ;
2025-07-06 20:43:53 +03:00
} ) ;
2025-12-02 16:09:30 +03:00
CLAY_AUTO_ID ( {
2025-07-06 20:43:53 +03:00
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
. 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 - > backColor2 )
2025-07-06 20:43:53 +03:00
} ) {
2026-03-27 09:51:53 +03:00
constexpr float entryHeight = 25.0f ;
2026-04-30 14:45:36 +03:00
filePicker . entriesScrollArea = gui . element < ManyElementScrollArea > ( " file picker entries " , ManyElementScrollArea : : Options {
2026-03-27 09:51:53 +03:00
. entryHeight = entryHeight ,
. entryCount = filePicker . entries . size ( ) ,
2026-03-31 21:21:49 +03:00
. clipHorizontal = false ,
2026-03-27 09:51:53 +03:00
. elementContent = [ & ] ( size_t i ) {
const std : : filesystem : : path & entry = filePicker . entries [ i ] ;
bool selectedEntry = filePicker . currentSelectedPath = = entry ;
2026-04-02 21:57:26 +03:00
gui . element < LayoutElement > ( " elem " , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
2026-03-31 17:33:09 +03:00
CLAY ( lId , {
2026-03-27 09:51:53 +03:00
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_FIXED ( entryHeight ) } ,
. childGap = 1 ,
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_CENTER } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT
} ,
2026-03-31 13:17:58 +03:00
. backgroundColor = selectedEntry ? convert_vec4 < Clay_Color > ( io . theme - > backColor1 ) : convert_vec4 < Clay_Color > ( io . theme - > backColor2 )
2026-03-27 09:51:53 +03:00
} ) {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_FIXED ( 20 ) , . height = CLAY_SIZING_FIXED ( 20 ) }
} ,
} ) {
if ( std : : filesystem : : is_directory ( entry ) )
gui . element < SVGIcon > ( " folder icon " , " data/icons/folder.svg " , selectedEntry ) ;
else
gui . element < SVGIcon > ( " file icon " , " data/icons/file.svg " , selectedEntry ) ;
2025-07-06 20:43:53 +03:00
}
2026-03-27 09:51:53 +03:00
text_label ( gui , entry . filename ( ) . string ( ) ) ;
2025-07-06 20:43:53 +03:00
}
2026-03-27 09:51:53 +03:00
} , LayoutElement : : Callbacks {
2026-04-28 20:28:21 +03:00
. onClick = [ & , selectedEntry , entry ] ( LayoutElement * l , const InputManager : : MouseButtonCallbackArgs & button ) {
2026-04-02 21:57:26 +03:00
if ( l - > mouseHovering & & button . button = = InputManager : : MouseButton : : LEFT & & button . down ) {
2026-03-27 09:51:53 +03:00
gui . set_post_callback_func ( [ & , button , selectedEntry , entry ] {
if ( selectedEntry & & button . clicks > = 2 ) {
2026-04-01 21:49:44 +03:00
if ( std : : filesystem : : is_directory ( entry ) ) {
2026-04-12 12:36:00 +03:00
main . conf . currentSearchPath = entry ;
2026-04-01 21:49:44 +03:00
file_picker_gui_refresh_entries ( ) ;
}
2026-03-27 09:51:53 +03:00
else if ( std : : filesystem : : is_regular_file ( entry ) )
file_picker_gui_done ( ) ;
}
else {
filePicker . currentSelectedPath = entry ;
if ( std : : filesystem : : is_regular_file ( entry ) )
filePicker . fileName = entry . filename ( ) . string ( ) ;
}
} ) ;
gui . set_to_layout ( ) ;
}
2025-07-06 20:43:53 +03:00
}
2026-03-27 09:51:53 +03:00
} ) ;
2025-07-06 20:43:53 +03:00
}
2026-04-30 14:45:36 +03:00
} ) - > scrollArea ;
2025-07-06 20:43:53 +03:00
}
2026-03-27 09:51:53 +03:00
left_to_right_line_layout ( gui , [ & ] ( ) {
input_text ( gui , " filepicker filename " , & filePicker . fileName ) ;
gui . element < DropDown < size_t > > ( " filepicker select type " , & filePicker . extensionSelected , filePicker . extensionFilters , DropdownOptions {
. onClick = [ & ] { file_picker_gui_refresh_entries ( ) ; }
} ) ;
2025-07-06 20:43:53 +03:00
} ) ;
2026-03-27 09:51:53 +03:00
left_to_right_line_layout ( gui , [ & ] ( ) {
text_button_wide ( " filepicker done " , " Done " , [ & ] { file_picker_gui_done ( ) ; } ) ;
text_button_wide ( " filepicker cancel " , " Cancel " , [ & ] { filePicker . isOpen = false ; } ) ;
2025-07-06 20:43:53 +03:00
} ) ;
2026-03-27 09:51:53 +03:00
} ) ;
2026-03-04 12:15:24 +03:00
}