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"
# include "DrawingProgram/Tools/ScreenshotTool.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"
# include "GUIStuff/ElementHelpers/ScrollAreaHelpers.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"
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 <fstream>
# 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-03-27 09:51:53 +03:00
2025-10-01 20:08:01 +03:00
# define UPDATE_DOWNLOAD_URL "https: //infinipaint.com/download.html"
# define UPDATE_NOTIFICATION_URL "https: //infinipaint.com/updateNotificationVersion.txt"
2025-07-06 20:43:53 +03:00
# ifdef __EMSCRIPTEN__
# include <EmscriptenHelpers/emscripten_browser_file.h>
# endif
Toolbar : : NativeFilePicker Toolbar : : nativeFilePicker ;
2026-03-27 09:51:53 +03:00
using namespace GUIStuff ;
using namespace ElementHelpers ;
2025-07-06 20:43:53 +03:00
Toolbar : : Toolbar ( MainProgram & initMain ) :
main ( initMain )
{
load_default_palette ( ) ;
}
void Toolbar : : load_default_palette ( ) {
paletteData . palettes . clear ( ) ;
paletteData . palettes . emplace_back ( ) ;
auto & palette = paletteData . palettes . back ( ) . colors ;
paletteData . palettes . back ( ) . name = " Default " ;
palette = { { 1.0 , 1.0 , 1.0 } , { 0.0 , 0.0 , 0.0 } , { 1.0 , 0.0 , 0.0 } , { 1.0 , 0.529411792755127 , 0.0 } , { 1.0 , 0.8274509906768799 , 0.0 } , { 0.8705882430076599 , 1.0 , 0.03921568766236305 } , { 0.6313725709915161 , 1.0 , 0.03921568766236305 } , { 0.03921568766236305 , 1.0 , 0.6000000238418579 } , { 0.03921568766236305 , 0.9372549057006836 , 1.0 } , { 0.0784313753247261 , 0.4901960790157318 , 0.9607843160629272 } , { 0.3450980484485626 , 0.03921568766236305 , 1.0 } , { 0.7450980544090271 , 0.03921568766236305 , 1.0 } } ;
paletteData . selectedPalette = 0 ;
}
void Toolbar : : save_palettes ( ) {
std : : ofstream f ( main . configPath / " palettes.json " ) ;
if ( f . is_open ( ) ) {
using json = nlohmann : : json ;
json j ;
auto palettesToSave = paletteData . palettes ;
palettesToSave . erase ( palettesToSave . begin ( ) ) ;
j = palettesToSave ;
f < < j ;
f . close ( ) ;
}
}
void Toolbar : : load_palettes ( ) {
std : : ifstream f ( main . configPath / " palettes.json " ) ;
load_default_palette ( ) ;
if ( f . is_open ( ) ) {
using json = nlohmann : : json ;
try {
json j ;
f > > j ;
std : : vector < PaletteData : : Palette > palettes ;
j . get_to ( palettes ) ;
paletteData . palettes . insert ( paletteData . palettes . end ( ) , palettes . begin ( ) , palettes . end ( ) ) ;
} catch ( . . . ) { }
f . close ( ) ;
}
}
// You can't trust that filter wont be -1 on the platform youre on, so dont use the extension from the callback
void Toolbar : : sdl_open_file_dialog_callback ( void * userData , const char * const * fileList , int filter ) {
if ( ! fileList ) {
nativeFilePicker . isOpen = false ;
return ;
}
if ( ! ( * fileList ) ) {
nativeFilePicker . isOpen = false ;
return ;
}
ExtensionFilter e ;
if ( filter > = 0 )
e = nativeFilePicker . extensionFiltersComplete [ filter ] ;
nativeFilePicker . postSelectionFunc ( fileList [ 0 ] , e ) ;
nativeFilePicker . isOpen = false ;
}
void Toolbar : : open_file_selector ( const std : : string & filePickerName , const std : : vector < ExtensionFilter > & extensionFilters , OpenFileSelectorCallback postSelectionFunc , const std : : string & fileName , bool isSaving ) {
2026-03-31 13:17:58 +03:00
if ( main . conf . useNativeFilePicker ) {
2025-07-06 20:43:53 +03:00
if ( ! nativeFilePicker . isOpen ) {
nativeFilePicker . postSelectionFunc = postSelectionFunc ;
nativeFilePicker . extensionFiltersComplete = extensionFilters ;
nativeFilePicker . sdlFileFilters . clear ( ) ;
for ( auto & e : nativeFilePicker . extensionFiltersComplete )
nativeFilePicker . sdlFileFilters . emplace_back ( e . name . c_str ( ) , e . extensions . c_str ( ) ) ;
if ( isSaving )
SDL_ShowSaveFileDialog ( sdl_open_file_dialog_callback , nullptr , main . window . sdlWindow , nativeFilePicker . sdlFileFilters . data ( ) , nativeFilePicker . sdlFileFilters . size ( ) , nullptr ) ;
else
SDL_ShowOpenFileDialog ( sdl_open_file_dialog_callback , nullptr , main . window . sdlWindow , nativeFilePicker . sdlFileFilters . data ( ) , nativeFilePicker . sdlFileFilters . size ( ) , nullptr , false ) ;
}
}
else {
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 = " " ;
2026-04-01 21:49:44 +03:00
filePicker . isSaving = isSaving ;
2026-04-01 22:28:48 +03:00
filePicker . entriesScrollArea = nullptr ;
2026-03-29 20:23:38 +03:00
file_picker_gui_refresh_entries ( ) ;
2025-07-06 20:43:53 +03:00
}
}
std : : filesystem : : path & Toolbar : : file_selector_path ( ) {
return filePicker . currentSearchPath ;
}
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
}
void Toolbar : : load_licenses ( ) {
2026-02-24 17:21:41 +03:00
{
int globCount ;
std : : filesystem : : path third_party_license_path ( " data/third_party_licenses " ) ;
char * * filesInPath = SDL_GlobDirectory ( third_party_license_path . c_str ( ) , nullptr , 0 , & globCount ) ;
if ( filesInPath ) {
for ( int i = 0 ; i < globCount ; i + + ) {
std : : filesystem : : path filePath = third_party_license_path / std : : filesystem : : path ( filesInPath [ i ] ) ;
SDL_PathInfo fileInfo ;
if ( SDL_GetPathInfo ( filePath . c_str ( ) , & fileInfo ) & & fileInfo . type = = SDL_PATHTYPE_FILE ) {
thirdPartyLicenses . emplace_back ( filePath . filename ( ) . string ( ) , read_file_to_string ( filePath ) ) ;
}
}
SDL_free ( filesInPath ) ;
}
2025-07-06 20:43:53 +03:00
}
2026-02-24 17:21:41 +03:00
2025-07-06 20:43:53 +03:00
std : : sort ( thirdPartyLicenses . begin ( ) , thirdPartyLicenses . end ( ) , [ ] ( const auto & a1 , const auto & a2 ) {
return std : : lexicographical_compare ( a1 . first . begin ( ) , a1 . first . end ( ) , a2 . first . begin ( ) , a2 . first . end ( ) ) ;
} ) ;
2025-11-16 16:39:12 +03:00
ownLicenseText = " InfiniPaint v " + VersionConstants : : CURRENT_VERSION_STRING ;
2025-10-30 11:26:00 +03:00
ownLicenseText + =
2025-07-06 20:43:53 +03:00
R " (
2026-02-03 12:16:39 +03:00
Copyright © 2026 Yousef Khadadeh
2025-07-06 20:43:53 +03:00
Permission is hereby granted , free of charge , to any person obtaining a copy of this software and associated documentation files ( the “ Software ” ) , to deal in the Software without restriction , including without limitation the rights to use , copy , modify , merge , publish , distribute , sublicense , and / or sell copies of the Software , and to permit persons to whom the Software is furnished to do so , subject to the following conditions :
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software .
THE SOFTWARE IS PROVIDED “ AS IS ” , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY , FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM , OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE .
) " ;
}
2026-04-05 22:01:30 +03:00
void Toolbar : : update ( ) {
std : : erase_if ( main . logMessages , [ & ] ( auto & logM ) {
logM . time . update_time_since ( ) ;
if ( logM . time > LogMessage : : FADE_START_TIME ) {
main . g . gui . set_to_layout ( ) ;
return logM . time > = LogMessage : : DISPLAY_TIME ;
}
return false ;
} ) ;
}
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 ;
2025-10-01 20:08:01 +03:00
# ifndef __EMSCRIPTEN__
update_notification_check ( ) ;
# endif
2025-07-06 20:43:53 +03:00
if ( main . drawGui ) {
2025-12-02 16:09:30 +03:00
CLAY_AUTO_ID ( {
2025-07-06 20:43:53 +03:00
. layout = {
2026-03-31 13:17:58 +03:00
. sizing = { . width = CLAY_SIZING_FIT ( gui . io . windowSize . x ( ) ) , . height = CLAY_SIZING_FIT ( gui . io . windowSize . y ( ) ) } ,
. 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 ( ) ;
2025-10-01 20:08:01 +03:00
# ifndef __EMSCRIPTEN__
else if ( updateCheckerData . showGui )
update_notification_gui ( ) ;
# endif
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 )
still_connecting_center_message ( ) ;
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 ( ) )
no_layers_being_edited_message ( ) ;
2025-07-06 20:43:53 +03:00
if ( showPerformance )
performance_metrics ( ) ;
}
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 )
main . world - > drawProg . right_click_popup_gui ( ) ;
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 ;
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 ,
. showScrollbarY = true ,
. 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 ( ) ,
. palette = paletteData . palettes [ paletteData . selectedPalette ] . colors ,
. 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 ( ) ;
bool layerMenuPopUpJustOpen = false ;
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-04-06 12:23:46 +03:00
Element * gridMenuButton = icon_button_top_toolbar ( " Grids Button " , " data/icons/grid.svg " , gridMenu . popupOpen , [ & ] {
2026-03-30 18:51:14 +03:00
if ( gridMenu . popupOpen )
stop_displaying_grid_menu ( ) ;
2026-04-06 12:23:46 +03:00
else
2026-03-30 18:51:14 +03:00
gridMenu . popupOpen = true ;
} ) ;
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-03-30 18:51:14 +03:00
if ( gridMenu . popupOpen )
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-05 20:26:03 +03:00
CustomEvents : : emit_open_infinipaint_file_event ( {
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__
static std : : weak_ptr < World > worldWeakPtr ;
worldWeakPtr = make_weak_ptr ( main . world ) ;
emscripten_browser_file : : upload ( " * " , [ ] ( std : : string const & fileName , std : : string const & mimeType , std : : string_view buffer , void * callbackData ) {
if ( ! buffer . empty ( ) ) {
auto world = ( ( std : : weak_ptr < World > * ) callbackData ) - > lock ( ) ;
if ( world )
world - > drawProg . add_file_to_canvas_by_data ( fileName , buffer , world - > main . window . size . cast < float > ( ) / 2.0f ) ;
else
Logger : : get ( ) . log ( " INFO " , " Loading image to canvas that has been destroyed " ) ;
}
} , & worldWeakPtr ) ;
# else
open_file_selector ( " Open File " , { { " Any File " , " * " } } , [ w = make_weak_ptr ( main . world ) ] ( const std : : filesystem : : path & p , const auto & e ) {
auto wLock = w . lock ( ) ;
if ( wLock )
wLock - > drawProg . add_file_to_canvas_by_path ( p , wLock - > main . window . size . cast < float > ( ) / 2.0f , false ) ;
2026-03-30 18:51:14 +03:00
else
Logger : : get ( ) . log ( " INFO " , " Loading image to canvas that has been destroyed " ) ;
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-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-02 21:57:26 +03:00
. mouseButton = [ & , mainMenuButton ] ( LayoutElement * l , const InputManager : : MouseButtonCallbackArgs & button ) {
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-01-09 21:56:58 +03:00
void Toolbar : : still_connecting_center_message ( ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
auto & io = gui . io ;
2026-01-09 21:56:58 +03:00
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_FIT ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
2026-03-31 13:17:58 +03:00
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
. childGap = io . theme - > childGap1 ,
2026-01-09 21:56:58 +03:00
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
2026-03-31 13:17:58 +03:00
. backgroundColor = convert_vec4 < Clay_Color > ( io . theme - > backColor1 ) ,
. cornerRadius = CLAY_CORNER_RADIUS ( io . theme - > windowCorners1 ) ,
2026-01-09 21:56:58 +03:00
. floating = { . attachPoints = { . element = CLAY_ATTACH_POINT_CENTER_CENTER , . parent = CLAY_ATTACH_POINT_CENTER_CENTER } , . attachTo = CLAY_ATTACH_TO_PARENT }
} ) {
2026-03-27 09:51:53 +03:00
text_label ( gui , " Connecting to server... " ) ;
2026-01-09 21:56:58 +03:00
}
}
void Toolbar : : no_layers_being_edited_message ( ) {
2026-03-31 13:17:58 +03:00
auto & gui = main . g . gui ;
auto & io = gui . io ;
2026-01-09 21:56:58 +03:00
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_FIT ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
2026-03-31 13:17:58 +03:00
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
. childGap = io . theme - > childGap1 ,
2026-01-09 21:56:58 +03:00
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
2026-03-31 13:17:58 +03:00
. backgroundColor = convert_vec4 < Clay_Color > ( io . theme - > backColor1 ) ,
. cornerRadius = CLAY_CORNER_RADIUS ( io . theme - > windowCorners1 ) ,
2026-01-09 21:56:58 +03:00
. floating = { . attachPoints = { . element = CLAY_ATTACH_POINT_CENTER_CENTER , . parent = CLAY_ATTACH_POINT_CENTER_CENTER } , . attachTo = CLAY_ATTACH_TO_PARENT }
} ) {
2026-03-27 09:51:53 +03:00
text_label ( gui , " Select a layer to edit " ) ;
2026-01-09 21:56:58 +03:00
}
}
2025-10-01 20:08:01 +03:00
# ifndef __EMSCRIPTEN__
void Toolbar : : update_notification_check ( ) {
if ( ! updateCheckerData . updateCheckDone ) {
2026-03-31 13:17:58 +03:00
if ( main . conf . checkForUpdates ) {
2025-10-01 20:31:03 +03:00
if ( ! updateCheckerData . versionFile )
updateCheckerData . versionFile = FileDownloader : : download_data_from_url ( UPDATE_NOTIFICATION_URL ) ;
else {
switch ( updateCheckerData . versionFile - > status ) {
case FileDownloader : : DownloadData : : Status : : IN_PROGRESS :
break ;
case FileDownloader : : DownloadData : : Status : : SUCCESS : {
updateCheckerData . updateCheckDone = true ;
std : : optional < VersionNumber > newVersion = version_str_to_version_numbers ( updateCheckerData . versionFile - > str ) ;
2025-11-16 16:39:12 +03:00
std : : optional < VersionNumber > currentVersion = VersionConstants : : CURRENT_VERSION_NUMBER ;
2025-10-01 20:31:03 +03:00
if ( newVersion . has_value ( ) & & currentVersion . has_value ( ) ) {
VersionNumber & newV = newVersion . value ( ) ;
VersionNumber & currentV = currentVersion . value ( ) ;
updateCheckerData . newVersionStr = version_numbers_to_version_str ( newV ) ;
Logger : : get ( ) . log ( " INFO " , " Latest online version is v " + updateCheckerData . newVersionStr ) ;
2026-03-31 18:41:19 +03:00
if ( newV > currentV ) {
2025-10-01 20:31:03 +03:00
updateCheckerData . showGui = true ;
2026-03-31 18:41:19 +03:00
main . g . gui . set_to_layout ( ) ;
}
2025-10-01 20:31:03 +03:00
else if ( newV = = currentV )
Logger : : get ( ) . log ( " INFO " , " Current version is up to date " ) ;
else
Logger : : get ( ) . log ( " INFO " , " Local version has larger version number than the latest online one " ) ;
}
2025-10-01 20:08:01 +03:00
else
2025-10-01 20:31:03 +03:00
Logger : : get ( ) . log ( " INFO " , " Update notification file couldn't be converted to version numbers " ) ;
updateCheckerData . versionFile = nullptr ;
break ;
2025-10-01 20:08:01 +03:00
}
2025-10-01 20:31:03 +03:00
case FileDownloader : : DownloadData : : Status : : FAILURE :
Logger : : get ( ) . log ( " INFO " , " Failed to check for updates " ) ;
updateCheckerData . updateCheckDone = true ;
updateCheckerData . versionFile = nullptr ;
break ;
2025-10-01 20:08:01 +03:00
}
}
}
2025-10-01 20:31:03 +03:00
else
updateCheckerData . updateCheckDone = true ;
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 " , [ & ] {
text_label_centered ( gui , " Update v " + updateCheckerData . newVersionStr + " available! " ) ;
text_button ( gui , " download " , " Open download page in web browser " , {
. wide = true ,
. onClick = [ & ] {
SDL_OpenURL ( UPDATE_DOWNLOAD_URL ) ;
updateCheckerData . showGui = false ;
}
} ) ;
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-03-30 18:51:14 +03:00
updateCheckerData . showGui = false ;
}
} ) ;
text_button ( gui , " ignore for now " , " Ignore for now " , {
. wide = true ,
. onClick = [ & ] {
updateCheckerData . showGui = false ;
}
} ) ;
2026-03-27 09:51:53 +03:00
} ) ;
2026-03-31 18:41:19 +03:00
} ) ;
2025-10-01 20:08:01 +03:00
}
# endif
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 " ) ;
float ENTRY_HEIGHT = 25.0f ;
if ( main . world - > gridMan . grids - > empty ( ) )
text_label_centered ( gui , " No grids yet... " ) ;
scroll_area_many_entries ( gui , " grid menu entries " , {
. entryHeight = ENTRY_HEIGHT ,
. entryCount = main . world - > gridMan . grids - > size ( ) ,
. clipHorizontal = true ,
. elementContent = [ & ] ( size_t i ) {
auto & grid = main . world - > gridMan . grids - > at ( i ) - > obj ;
bool selectedEntry = gridMenu . selectedGrid = = i ;
gui . element < LayoutElement > ( " elem " , [ & ] ( LayoutElement * , const Clay_ElementId & lId2 ) {
CLAY ( lId2 , {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_FIXED ( ENTRY_HEIGHT ) } ,
. childGap = 2 ,
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_CENTER } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT
} ,
. backgroundColor = selectedEntry ? convert_vec4 < Clay_Color > ( io . theme - > backColor1 ) : convert_vec4 < Clay_Color > ( io . theme - > backColor2 )
} ) {
text_label ( gui , grid - > get_display_name ( ) ) ;
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
. childGap = 1 ,
. childAlignment = { . x = CLAY_ALIGN_X_RIGHT , . y = CLAY_ALIGN_Y_CENTER } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT
}
} ) {
auto list_button = [ & ] ( const char * id , const char * svgPath , const std : : function < void ( ) > & onClick ) {
gui . set_z_index_keep_clipping_region ( gui . get_z_index ( ) + 1 , [ & ] {
svg_icon_button ( gui , id , svgPath , {
. drawType = SelectableButton : : DrawType : : TRANSPARENT_ALL ,
. size = ENTRY_HEIGHT ,
. onClick = onClick
} ) ;
} ) ;
} ;
list_button ( " visibility eye " , grid - > visible ? " data/icons/eyeopen.svg " : " data/icons/eyeclose.svg " , [ & ] {
grid - > visible = ! grid - > visible ;
NetworkingObjects : : generic_serialized_class_send_update_to_all < WorldGrid > ( grid ) ;
} ) ;
list_button ( " edit pencil " , " data/icons/pencil.svg " , [ & ] {
main . world - > drawProg . modify_grid ( grid ) ;
stop_displaying_grid_menu ( ) ;
} ) ;
list_button ( " delete trash " , " data/icons/trash.svg " , [ & , i ] {
main . world - > gridMan . remove_grid ( i ) ;
} ) ;
}
}
} , LayoutElement : : Callbacks {
. mouseButton = [ & , i ] ( LayoutElement * l , const InputManager : : MouseButtonCallbackArgs & button ) {
if ( l - > mouseHovering & & button . down & & button . button = = InputManager : : MouseButton : : LEFT ) {
gridMenu . selectedGrid = i ;
if ( button . clicks = = 2 ) {
main . world - > drawProg . modify_grid ( grid ) ;
stop_displaying_grid_menu ( ) ;
}
gui . set_to_layout ( ) ;
}
}
} ) ;
}
} ) ;
left_to_right_line_layout ( gui , [ & ] ( ) {
input_text ( gui , " grid text input " , & gridMenu . newName , {
. onEnter = [ & ] ( ) { add_grid ( ) ; }
} ) ;
svg_icon_button ( gui , " grid add button " , " data/icons/plusbold.svg " , {
. size = SMALL_BUTTON_SIZE ,
. onClick = [ & ] { add_grid ( ) ; }
} ) ;
} ) ;
}
} , LayoutElement : : Callbacks {
. mouseButton = [ & , gridMenuButton ] ( LayoutElement * l , const InputManager : : MouseButtonCallbackArgs & button ) {
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
}
}
2026-04-06 12:23:46 +03:00
void Toolbar : : add_grid ( ) {
if ( ! gridMenu . newName . empty ( ) ) {
main . world - > gridMan . add_default_grid ( gridMenu . newName ) ;
main . world - > drawProg . modify_grid ( main . world - > gridMan . grids - > at ( main . world - > gridMan . grids - > size ( ) - 1 ) - > obj ) ;
stop_displaying_grid_menu ( ) ;
}
}
2025-09-12 09:54:49 +03:00
void Toolbar : : stop_displaying_grid_menu ( ) {
gridMenu . newName . clear ( ) ;
gridMenu . popupOpen = false ;
2025-12-10 13:06:45 +03:00
gridMenu . selectedGrid = std : : numeric_limits < uint32_t > : : max ( ) ;
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 {
. mouseButton = [ & , bookmarkMenuButton ] ( LayoutElement * l , const InputManager : : MouseButtonCallbackArgs & button ) {
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 {
. mouseButton = [ & , layerMenuButton ] ( LayoutElement * l , const InputManager : : MouseButtonCallbackArgs & button ) {
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
}
2025-10-16 09:38:58 +03:00
std : : unique_ptr < skia : : textlayout : : Paragraph > 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 ;
2025-10-16 09:38:58 +03:00
skia : : textlayout : : ParagraphStyle pStyle ;
pStyle . setTextAlign ( skia : : textlayout : : TextAlign : : kLeft ) ;
skia : : textlayout : : TextStyle tStyle ;
2026-03-31 13:17:58 +03:00
tStyle . setFontSize ( io . fontSize ) ;
2025-11-16 10:42:01 +03:00
tStyle . setFontFamilies ( main . fonts - > get_default_font_families ( ) ) ;
2025-10-16 09:38:58 +03:00
tStyle . setFontStyle ( SkFontStyle : : Bold ( ) ) ;
2026-03-31 13:17:58 +03:00
tStyle . setForegroundColor ( SkPaint { color_mul_alpha ( message . type = = ChatMessage : : JOIN ? io . theme - > warningColor : io . theme - > frontColor1 , alpha ) } ) ;
2025-10-16 09:38:58 +03:00
pStyle . setTextStyle ( tStyle ) ;
2026-03-31 13:17:58 +03:00
skia : : textlayout : : ParagraphBuilderImpl a ( pStyle , io . fonts - > collection , SkUnicodes : : ICU : : Make ( ) ) ;
2025-10-16 09:38:58 +03:00
if ( message . type = = ChatMessage : : JOIN ) {
std : : string messageName = message . name + " " ;
a . addText ( messageName . c_str ( ) , messageName . length ( ) ) ;
}
else {
std : : string messageName = " [ " + message . name + " ] " ;
a . addText ( messageName . c_str ( ) , messageName . length ( ) ) ;
}
tStyle . setFontStyle ( SkFontStyle : : Normal ( ) ) ;
a . pushStyle ( tStyle ) ;
a . addText ( message . message . c_str ( ) , message . message . length ( ) ) ;
return a . Build ( ) ;
}
2025-07-06 20:43:53 +03:00
void Toolbar : : chat_box ( ) {
2026-03-27 09:51:53 +03:00
//constexpr float CHATBOX_WIDTH = 700;
//if(main.world->netObjMan.is_connected()) {
// Clay_ElementId localId = CLAY_ID_LOCAL("INFINIPAINT CHAT BOX OPEN BUTTON");
// CLAY(localId, {
// .layout = {
// .layoutDirection = CLAY_LEFT_TO_RIGHT
// },
2026-03-31 13:17:58 +03:00
// .floating = {.offset = {static_cast<float>(io.theme->padding1), -static_cast<float>(io.theme->padding1)}, .attachPoints = {.element = CLAY_ATTACH_POINT_LEFT_BOTTOM, .parent = CLAY_ATTACH_POINT_LEFT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT}
2026-03-27 09:51:53 +03:00
// }) {
// gui.obstructing_window(localId);
// gui.push_id("chat box open button");
// if(gui.svg_icon_button("Chat Open Button", "data/icons/chat.svg", chatBoxState != CHATBOXSTATE_CLOSE)) {
// if(chatBoxState == CHATBOXSTATE_CLOSE)
// chatBoxState = CHATBOXSTATE_JUSTOPEN;
// else
// chatBoxState = CHATBOXSTATE_CLOSE;
// }
// gui.pop_id();
// }
//}
//Clay_ElementId localId = CLAY_ID_LOCAL("INFINIPAINT CHAT BOX OPEN");
//CLAY(localId, {
// .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
// },
2026-03-31 13:17:58 +03:00
// .floating = {.offset = {60 + static_cast<float>(io.theme->padding1), -static_cast<float>(io.theme->padding1)}, .attachPoints = {.element = CLAY_ATTACH_POINT_LEFT_BOTTOM, .parent = CLAY_ATTACH_POINT_LEFT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT}
2026-03-27 09:51:53 +03:00
//}) {
// gui.obstructing_window(localId);
// gui.push_id("chat box");
// if(chatBoxState == CHATBOXSTATE_JUSTOPEN || chatBoxState == CHATBOXSTATE_OPEN) {
// gui.push_id("messages");
// 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
// },
2026-03-31 13:17:58 +03:00
// .backgroundColor = convert_vec4<Clay_Color>(io.theme->backColor1)
2026-03-27 09:51:53 +03:00
// }) {
// int id = 0;
// for(auto& chatMessage : main.world->chatMessages | std::views::reverse) {
// gui.push_id(id);
// gui.text_paragraph("text", build_paragraph_from_chat_message(chatMessage, 1.0f), CHATBOX_WIDTH);
// gui.pop_id();
// id++;
// }
// }
// gui.pop_id();
// gui.left_to_right_line_layout([&]() {
// gui.input_text("message input", &chatMessageInput);
// if(gui.text_button("send button", "Send", false, true)) {
// if(!chatMessageInput.empty())
// main.world->send_chat_message(chatMessageInput);
// }
// });
// gui.push_id("message input");
// GUIStuff::GUIManager::ElementContainer& a = gui.elements[gui.idStack];
// GUIStuff::TextBox<std::string>& t = *(GUIStuff::TextBox<std::string>*)a.elem.get();
// gui.pop_id();
// if(chatBoxState == CHATBOXSTATE_JUSTOPEN) {
// t.selection.selected = true;
// chatBoxState = CHATBOXSTATE_OPEN;
// }
2026-03-31 13:17:58 +03:00
// if(io.key.escape)
2026-03-27 09:51:53 +03:00
// t.selection.selected = false;
2026-03-31 13:17:58 +03:00
// if(io.key.enter) {
2026-03-27 09:51:53 +03:00
// main.world->send_chat_message(chatMessageInput);
// t.selection.selected = false;
// }
// if(!t.selection.selected) {
// chatMessageInput.clear();
// chatBoxState = CHATBOXSTATE_CLOSE;
// }
// }
// else {
// constexpr float DISPLAY_TIME = 8.0f;
// constexpr float FADE_START_TIME = 7.0f;
// gui.push_id("messages popup");
// int id = 0;
// for(auto& chatMessage : main.world->chatMessages | std::views::reverse) {
// chatMessage.time.update_time_since();
// if(chatMessage.time < DISPLAY_TIME) {
// float a = 1.0f - lerp_time<float>(chatMessage.time, DISPLAY_TIME, FADE_START_TIME);
// Clay_ElementId elemId = CLAY_IDI_LOCAL("CHAT MESSAGE ID", id);
// CLAY(elemId, {
// .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
// },
2026-03-31 13:17:58 +03:00
// .backgroundColor = convert_vec4<Clay_Color>(color_mul_alpha(io.theme->backColor1, a)),
2026-03-27 09:51:53 +03:00
// }) {
// gui.push_id(id);
// gui.obstructing_window(elemId);
// gui.text_paragraph("text", build_paragraph_from_chat_message(chatMessage, a), CHATBOX_WIDTH);
// gui.pop_id();
// }
// }
// id++;
// }
// gui.pop_id();
// }
// gui.pop_id();
//}
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 ( ) ;
if ( logM . time < LogMessage : : DISPLAY_TIME ) {
float a = 1.0f - lerp_time < float > ( logM . time , LogMessage : : DISPLAY_TIME , LogMessage : : FADE_START_TIME ) ;
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 ) {
case LogMessage : : COLOR_NORMAL :
c = io . theme - > frontColor1 ;
break ;
case LogMessage : : COLOR_ERROR :
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
} ,
} ) {
main . world - > drawProg . toolbar_gui ( ) ;
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-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
2025-07-06 20:43:53 +03:00
main . world - > drawProg . tool_options_gui ( ) ;
}
}
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 ,
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_TOP } ,
. 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-06 12:23:46 +03:00
. mouseButton = [ & , b , color ] ( LayoutElement * l , const InputManager : : MouseButtonCallbackArgs & button ) {
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-03 09:52:45 +03:00
auto & palette = paletteData . palettes [ paletteData . selectedPalette ] . colors ;
2026-03-27 09:51:53 +03:00
2026-04-03 09:52:45 +03:00
gui . element < ScrollArea > ( " color palette scroll area " , ScrollArea : : Options {
. scrollVertical = true ,
. clipVertical = true ,
. showScrollbarY = true ,
. 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 ;
for ( auto & p : paletteData . palettes )
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 ;
}
} ) ;
svg_icon_button ( gui , " paletteremove " , " data/icons/close.svg " , {
. size = 25.0f ,
. onClick = [ & ] {
paletteData . palettes . erase ( paletteData . palettes . begin ( ) + paletteData . selectedPalette ) ;
paletteData . selectedPalette = 0 ;
}
} ) ;
}
if ( paletteData . addingPalette ) {
input_text_field ( gui , " paletteinputname " , " Name " , & paletteData . newPaletteStr ) ;
text_button_wide ( " addpalettebutton " , " Create " , [ & ] {
if ( ! paletteData . newPaletteStr . empty ( ) ) {
paletteData . palettes . emplace_back ( ) ;
paletteData . palettes . back ( ) . name = paletteData . newPaletteStr ;
paletteData . selectedPalette = paletteData . palettes . size ( ) - 1 ;
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 : : performance_metrics ( ) {
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_FIT ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
2026-03-31 13:17:58 +03:00
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
. childGap = io . theme - > childGap1 ,
2026-01-02 20:11:33 +03:00
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_BOTTOM } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT
2025-07-06 20:43:53 +03:00
} ,
2026-01-08 16:37:18 +03:00
. floating = { . offset = { - 10 , - 10 } , . attachPoints = { . element = CLAY_ATTACH_POINT_RIGHT_BOTTOM , . parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM } , . pointerCaptureMode = CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH , . attachTo = CLAY_ATTACH_TO_PARENT }
2025-07-06 20:43:53 +03:00
} ) {
2026-01-02 20:11:33 +03:00
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_FIT ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
2026-03-31 13:17:58 +03:00
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
. childGap = io . theme - > childGap1 ,
2026-01-02 20:11:33 +03:00
. childAlignment = { . x = CLAY_ALIGN_X_RIGHT , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
2026-03-31 13:17:58 +03:00
. backgroundColor = convert_vec4 < Clay_Color > ( color_mul_alpha ( io . theme - > backColor1 , 0.7f ) ) ,
2026-01-02 20:11:33 +03:00
} ) {
2026-03-27 09:51:53 +03:00
text_label ( gui , " Undo queue " ) ;
2026-01-02 21:06:13 +03:00
std : : vector < std : : string > undoList = main . world - > undo . get_front_undo_queue_names ( 10 ) ;
for ( const std : : string & u : undoList )
2026-03-27 09:51:53 +03:00
text_label ( gui , u ) ;
2026-01-02 20:11:33 +03:00
}
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_FIT ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
2026-03-31 13:17:58 +03:00
. padding = CLAY_PADDING_ALL ( io . theme - > padding1 ) ,
. childGap = io . theme - > childGap1 ,
2026-01-02 20:11:33 +03:00
. childAlignment = { . x = CLAY_ALIGN_X_RIGHT , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
2026-03-31 13:17:58 +03:00
. backgroundColor = convert_vec4 < Clay_Color > ( color_mul_alpha ( io . theme - > backColor1 , 0.7f ) ) ,
2026-01-02 20:11:33 +03:00
} ) {
std : : stringstream a ;
a < < " FPS: " < < std : : fixed < < std : : setprecision ( 0 ) < < ( 1.0 / main . deltaTime ) ;
2026-03-27 09:51:53 +03:00
text_label ( gui , a . str ( ) ) ;
text_label ( gui , " Item Count: " + std : : to_string ( main . world - > drawProg . layerMan . total_component_count ( ) ) ) ;
2026-01-02 20:11:33 +03:00
std : : stringstream b ;
b < < " Coord: " < < main . world - > drawData . cam . c . pos . x ( ) . display_int_str ( 5 ) < < " , " < < main . world - > drawData . cam . c . pos . y ( ) . display_int_str ( 5 ) ;
2026-03-27 09:51:53 +03:00
text_label ( gui , b . str ( ) ) ;
2026-01-02 20:11:33 +03:00
std : : stringstream c ;
c < < " Zoom: " < < main . world - > drawData . cam . c . inverseScale . display_int_str ( 5 ) ;
2026-03-27 09:51:53 +03:00
text_label ( gui , c . str ( ) ) ;
2026-01-02 20:11:33 +03:00
std : : stringstream d ;
d < < " Rotation: " < < main . world - > drawData . cam . c . rotation ;
2026-03-27 09:51:53 +03:00
text_label ( gui , d . str ( ) ) ;
2026-01-02 20:11:33 +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 ;
auto & io = gui . io ;
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 ;
uD - > main - > new_tab ( {
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
} , true ) ;
}
} , & 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-05 20:26:03 +03:00
CustomEvents : : emit_open_infinipaint_file_event ( {
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 ;
auto & io = gui . io ;
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 ;
auto & io = gui . io ;
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 ) , [ & ] {
input_text_field ( gui , " lobby " , " Lobby " , & serverToConnectTo ) ;
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 ) )
Logger : : get ( ) . log ( " USERINFO " , " Connect issue: Incorrect address length " ) ;
else if ( serverToConnectTo . substr ( 0 , NetLibrary : : GLOBALID_LEN ) = = NetLibrary : : get_global_id ( ) )
Logger : : get ( ) . log ( " USERINFO " , " Connect issue: Can't connect to your own address " ) ;
else {
2026-04-05 20:26:03 +03:00
CustomEvents : : emit_open_infinipaint_file_event ( {
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-03-31 13:17:58 +03:00
main . g . load_theme ( main . 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 ,
. showScrollbarY = true ,
. 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 " , [ & ] {
input_text_field ( gui , " display name input " , " Display name " , & main . conf . displayName ) ;
main . update_display_names ( ) ;
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 } ) ;
input_scalar_field ( gui , " Max GUI Scale " , " Max GUI Scale " , & main . conf . guiScale , 0.5f , 5.0f , { . decimalPrecision = 1 } ) ;
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 ) ;
slider_scalar_field ( gui , " smoothing time " , " Smoothing sampling time " , & main . conf . tabletOptions . smoothingSamplingTime , 0.001f , 1.0f , { . decimalPrecision = 3 } ) ;
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 } ) ;
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
checkbox_boolean_field ( gui , " mouse ignore when pen proximity " , " Ignore mouse movement when pen in proximity " , & tabletOptions . ignoreMouseMovementWhenPenInProximity ) ;
# endif
} ) ;
break ;
}
case GSETTINGS_THEME : {
general_scroll_area ( " theme " , [ & ] {
if ( ! themeData . selectedThemeIndex )
reload_theme_list ( ) ;
CLAY_AUTO_ID ( { . layout = {
. childGap = io . theme - > childGap1 ,
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_CENTER } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT ,
}
} ) {
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 ( ) ] ;
main . g . save_theme ( main . configPath , main . conf . themeCurrentlyLoaded ) ;
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 " , [ & ] {
try { std : : filesystem : : remove ( main . configPath / " themes " / ( themeData . themeDirList [ themeData . selectedThemeIndex . value ( ) ] + " .json " ) ) ; } catch ( . . . ) { }
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 " , [ & ] {
main . g . save_theme ( main . configPath , main . conf . themeCurrentlyLoaded ) ;
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 " , [ & ] {
checkbox_boolean_field ( gui , " show performance metrics " , " Show metrics " , & showPerformance ) ;
input_scalars_field ( gui , " jump transition easing " , " Jump easing " , & main . conf . jumpTransitionEasing , 4 , - 10.0f , 10.0f , { . decimalPrecision = 2 } ) ;
# ifndef __EMSCRIPTEN__
input_scalar_field ( gui , " fps cap slider " , " FPS cap " , & main . conf . fpsLimit , 3.0f , 10000.0f ) ;
# endif
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-03-31 13:17:58 +03:00
main . g . load_theme ( main . 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 ,
. showScrollbarY = true ,
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 " ) ;
for ( int i = 0 ; i < static_cast < int > ( thirdPartyLicenses . size ( ) ) ; i + + ) {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. padding = CLAY_PADDING_ALL ( 1 )
}
} ) {
gui . new_id ( i , [ & ] {
text_button ( gui , " noticebutton " , thirdPartyLicenses [ i ] . first , {
. 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 ,
. showScrollbarY = true ,
. innerContent = [ & ] ( const ScrollArea : : InnerContentParameters & ) {
text_label_size ( gui , ( selectedLicense = = - 1 ) ? ownLicenseText : thirdPartyLicenses [ selectedLicense ] . second , 0.8f ) ;
}
} ) ;
} ) ;
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 " ) ;
std : : filesystem : : path themeDir = main . configPath / " themes " ;
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-03-31 13:17:58 +03:00
if ( ! main . g . load_theme ( main . 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 {
for ( const std : : filesystem : : path & entry : std : : filesystem : : directory_iterator ( filePicker . currentSearchPath ) )
filePicker . entries . emplace_back ( entry ) ;
break ;
}
catch ( const std : : exception & e ) {
Logger : : get ( ) . log ( " INFO " , e . what ( ) ) ;
if ( filePicker . currentSearchPath = = main . homePath ) // The home path must exist. If we get errors on the home path, we have a real problem
throw e ;
filePicker . currentSearchPath = main . homePath ;
}
}
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 ( ) ) {
std : : filesystem : : path pathToRet = filePicker . 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 = [ & ] {
filePicker . currentSearchPath = filePicker . currentSearchPath . parent_path ( ) ;
file_picker_gui_refresh_entries ( ) ;
}
} ) ;
input_path_field ( gui , " file picker path " , " Path " , & filePicker . currentSearchPath , {
2026-04-07 19:15:58 +03:00
. updateEveryEdit = false ,
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-01 22:28:48 +03:00
filePicker . entriesScrollArea = scroll_area_many_entries ( gui , " file picker entries " , {
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-02 21:57:26 +03:00
. mouseButton = [ & , selectedEntry , entry ] ( LayoutElement * l , const InputManager : : MouseButtonCallbackArgs & button ) {
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-03-27 09:51:53 +03:00
filePicker . 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-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
}