2026-05-11 23:06:39 +03:00
/*
* InfiniPaint
* Copyright ( C ) 2025 - 2026 Yousef Khadadeh
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < https : //www.gnu.org/licenses/>.
*/
2026-04-12 17:31:32 +03:00
# include "FileSelectScreen.hpp"
2026-04-17 19:32:49 +03:00
# include "../MainProgram.hpp"
# include "../GUIHolder.hpp"
# include "../GUIStuff/ElementHelpers/TextLabelHelpers.hpp"
2026-04-21 18:38:24 +03:00
# include "../GUIStuff/ElementHelpers/ButtonHelpers.hpp"
2026-04-18 20:51:45 +03:00
# include "Helpers/ConvertVec.hpp"
# include "../GUIStuff/Elements/GridScrollArea.hpp"
2026-04-30 14:45:36 +03:00
# include "../GUIStuff/Elements/ManyElementScrollArea.hpp"
2026-04-19 17:44:48 +03:00
# include "../GUIStuff/Elements/ImageDisplay.hpp"
2026-04-22 17:40:58 +03:00
# include "../GUIStuff/Elements/LayoutElement.hpp"
2026-04-23 20:56:11 +03:00
# include "../GUIStuff/Elements/SVGIcon.hpp"
2026-04-25 19:01:19 +03:00
# include "../GUIStuff/Elements/TextParagraph.hpp"
2026-04-26 18:20:37 +03:00
# include "../GUIStuff/ElementHelpers/LayoutHelpers.hpp"
2026-05-12 21:13:31 +03:00
# include "../GUIStuff/ElementHelpers/ColorPickerHelpers.hpp"
# include "../GUIStuff/ElementHelpers/CheckBoxHelpers.hpp"
2026-05-15 09:51:25 +03:00
# include "../GUIStuff/ElementHelpers/NumberSliderHelpers.hpp"
2026-04-19 17:44:48 +03:00
# include "../World.hpp"
2026-04-20 18:56:39 +03:00
# include "Helpers/StringHelpers.hpp"
2026-04-26 13:21:05 +03:00
# include "PhoneDrawingProgramScreen.hpp"
2026-04-23 20:56:11 +03:00
# include <SDL3/SDL_time.h>
2026-04-20 20:16:15 +03:00
# include <SDL3/SDL_timer.h>
2026-05-13 14:31:35 +03:00
# include <algorithm>
2026-04-17 19:32:49 +03:00
2026-04-22 17:40:58 +03:00
# define MAIN_MENU_SIZE 300
using namespace GUIStuff ;
using namespace ElementHelpers ;
2026-04-23 20:56:11 +03:00
FileSelectScreen : : FileSelectScreen ( MainProgram & m ) : Screen ( m ) {
savePath = main . conf . configPath / " saves " ;
trashPath = main . conf . configPath / " trash " ;
2026-05-13 14:31:35 +03:00
infoPath = main . conf . configPath / " fileSelectInfo.json " ;
2026-04-23 20:56:11 +03:00
2026-04-27 16:48:44 +03:00
SDL_CreateDirectory ( savePath . string ( ) . c_str ( ) ) ;
SDL_CreateDirectory ( trashPath . string ( ) . c_str ( ) ) ;
2026-04-23 20:56:11 +03:00
try {
2026-05-13 14:31:35 +03:00
nlohmann : : json j ( nlohmann : : json : : parse ( read_file_to_string ( infoPath ) ) ) ;
j . get_to ( saveInfo ) ;
2026-04-23 20:56:11 +03:00
}
catch ( . . . ) { }
update_file_list ( fileList , savePath , false ) ;
// Update trash list once to get trash info up to date
std : : vector < FileInfo > trashListTemp ;
update_file_list ( trashListTemp , trashPath , true ) ;
}
2026-04-17 19:32:49 +03:00
2026-05-17 10:02:18 +03:00
void FileSelectScreen : : update ( ) {
std : : erase_if ( main . logMessages , [ & ] ( auto & logM ) {
logM . time . update_time_since ( ) ;
if ( logM . time > UserLogMessage : : FADE_START_TIME ) {
main . g . gui . set_to_layout ( ) ;
return logM . time > = UserLogMessage : : DISPLAY_TIME ;
}
return false ;
} ) ;
}
2026-04-17 19:32:49 +03:00
void FileSelectScreen : : gui_layout_run ( ) {
2026-04-23 20:56:11 +03:00
main_display ( ) ;
}
2026-05-15 09:19:26 +03:00
void FileSelectScreen : : update_file_list ( std : : vector < FileInfo > & fL , const std : : filesystem : : path & folderPath , bool trashUpdate ) {
2026-04-23 20:56:11 +03:00
constexpr SDL_Time NS_IN_DAY = SDL_SECONDS_TO_NS ( 24 * 3600 ) ;
2026-04-27 21:30:31 +03:00
constexpr int64_t DAYS_TO_DELETION = 30 ;
2026-04-23 20:56:11 +03:00
SDL_Time currentTime ;
SDL_GetCurrentTime ( & currentTime ) ;
fL . clear ( ) ;
int globCount ;
2026-05-15 09:19:26 +03:00
char * * filesInPath = SDL_GlobDirectory ( folderPath . string ( ) . c_str ( ) , " * " , 0 , & globCount ) ;
2026-04-23 20:56:11 +03:00
if ( filesInPath ) {
for ( int i = 0 ; i < globCount ; i + + ) {
std : : filesystem : : path p = filesInPath [ i ] ;
std : : string fExt = p . extension ( ) . string ( ) ;
2026-04-27 16:48:44 +03:00
if ( fExt = = World : : DOT_FILE_EXTENSION ) {
2026-05-15 09:19:26 +03:00
std : : filesystem : : path fullPath = folderPath / filesInPath [ i ] ;
2026-04-23 20:56:11 +03:00
FileInfo fileInfoToAdd ;
fileInfoToAdd . fileName = p . stem ( ) . string ( ) ;
if ( trashUpdate ) {
2026-05-13 14:31:35 +03:00
auto it = saveInfo . trash . files . find ( fileInfoToAdd . fileName ) ;
if ( it = = saveInfo . trash . files . end ( ) ) {
saveInfo . trash . files [ fileInfoToAdd . fileName ] . trashTime = currentTime ;
2026-04-23 20:56:11 +03:00
fileInfoToAdd . lastModifyTime = currentTime ;
}
else
fileInfoToAdd . lastModifyTime = it - > second . trashTime ;
2026-04-27 21:30:31 +03:00
int64_t daysSinceMovedToTrash = ( currentTime - fileInfoToAdd . lastModifyTime ) / NS_IN_DAY ;
if ( daysSinceMovedToTrash > = DAYS_TO_DELETION ) {
2026-04-23 20:56:11 +03:00
// Remove file and thumbnail
2026-05-15 09:19:26 +03:00
std : : filesystem : : path thumbnailPath = folderPath / ( fileInfoToAdd . fileName + " .jpg " ) ;
2026-05-16 21:56:58 +03:00
Logger : : get ( ) . log ( Logger : : LogType : : INFO , " Deleting file in trash: " + fullPath . string ( ) + " with thumbnail: " + thumbnailPath . string ( ) ) ;
2026-04-23 20:56:11 +03:00
SDL_RemovePath ( fullPath . string ( ) . c_str ( ) ) ;
2026-05-15 09:19:26 +03:00
SDL_RemovePath ( thumbnailPath . string ( ) . c_str ( ) ) ;
2026-05-13 14:31:35 +03:00
saveInfo . trash . files . erase ( it ) ;
2026-04-23 20:56:11 +03:00
continue ;
}
2026-04-27 21:30:31 +03:00
else if ( daysSinceMovedToTrash = = 29 )
2026-04-23 20:56:11 +03:00
fileInfoToAdd . lastModifyDate = " Today " ;
2026-04-27 21:30:31 +03:00
else if ( daysSinceMovedToTrash = = 28 )
2026-04-23 20:56:11 +03:00
fileInfoToAdd . lastModifyDate = " Tomorrow " ;
else
2026-04-27 21:30:31 +03:00
fileInfoToAdd . lastModifyDate = std : : to_string ( DAYS_TO_DELETION - daysSinceMovedToTrash - 1 ) + " days " ;
2026-04-23 20:56:11 +03:00
}
else {
SDL_PathInfo pathInfo ;
if ( SDL_GetPathInfo ( fullPath . string ( ) . c_str ( ) , & pathInfo ) ) {
SDL_DateTime pathDt ;
fileInfoToAdd . lastModifyTime = pathInfo . modify_time ;
if ( SDL_TimeToDateTime ( pathInfo . modify_time , & pathDt , true ) ) {
fileInfoToAdd . lastModifyDate = sdl_time_to_nice_access_time ( pathDt , main . conf . dateFormat , main . conf . timeFormat ) ;
}
}
}
fL . emplace_back ( fileInfoToAdd ) ;
}
}
}
else
2026-05-15 09:19:26 +03:00
SDL_CreateDirectory ( folderPath . string ( ) . c_str ( ) ) ;
2026-05-13 14:31:35 +03:00
sort_file_list ( fL ) ;
}
2026-05-17 10:02:18 +03:00
void FileSelectScreen : : global_log ( ) {
auto & gui = main . g . gui ;
auto & io = gui . io ;
2026-05-17 17:32:05 +03:00
float notificationSize = std : : min ( 240.0f , gui . io . safeWindowRect . width ( ) - 20.0f ) ;
2026-05-17 10:02:18 +03:00
gui . set_z_index ( gui . get_z_index ( ) + 100 , [ & ] {
gui . new_id ( " Global log popup list file select screen " , [ & ] {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_FIXED ( notificationSize ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. childGap = io . theme - > childGap1 ,
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_BOTTOM } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
. floating = { . offset = { 10 , - 10 } , . zIndex = gui . get_z_index ( ) , . attachPoints = { . element = CLAY_ATTACH_POINT_LEFT_BOTTOM , . parent = CLAY_ATTACH_POINT_LEFT_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 < UserLogMessage : : DISPLAY_TIME ) {
if ( logM . whereToDisplay = = UserLogMessage : : DISPLAY_DESKTOP_ONLY )
continue ;
float a = 1.0f - lerp_time < float > ( logM . time , UserLogMessage : : DISPLAY_TIME , UserLogMessage : : 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 ( notificationSize ) , . 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 UserLogMessage : : COLOR_NORMAL :
c = io . theme - > frontColor1 ;
break ;
case UserLogMessage : : COLOR_ERROR :
c = io . theme - > errorColor ;
break ;
}
text_label_color ( gui , logM . text , color_mul_alpha ( c , a ) ) ;
}
} ) ;
} ) ;
}
else
break ;
}
}
} ) ;
} ) ;
}
2026-05-13 14:31:35 +03:00
void FileSelectScreen : : sort_file_list ( std : : vector < FileInfo > & fL ) {
switch ( saveInfo . fileSort ) {
case SaveInfo : : FileSort : : ALPHABETICAL_ASCENDING :
std : : stable_sort ( fL . begin ( ) , fL . end ( ) , [ & ] ( const FileInfo & a , const FileInfo & b ) {
return std : : lexicographical_compare ( a . fileName . begin ( ) , a . fileName . end ( ) , b . fileName . begin ( ) , b . fileName . end ( ) ) ;
} ) ;
break ;
case SaveInfo : : FileSort : : ALPHABETICAL_DESCENDING :
std : : stable_sort ( fL . begin ( ) , fL . end ( ) , [ & ] ( const FileInfo & a , const FileInfo & b ) {
return std : : lexicographical_compare ( b . fileName . begin ( ) , b . fileName . end ( ) , a . fileName . begin ( ) , a . fileName . end ( ) ) ;
} ) ;
break ;
case SaveInfo : : FileSort : : TIME_ASCENDING :
std : : stable_sort ( fL . begin ( ) , fL . end ( ) , [ & ] ( const FileInfo & a , const FileInfo & b ) {
return b . lastModifyTime < a . lastModifyTime ;
} ) ;
break ;
case SaveInfo : : FileSort : : TIME_DESCENDING :
std : : stable_sort ( fL . begin ( ) , fL . end ( ) , [ & ] ( const FileInfo & a , const FileInfo & b ) {
return a . lastModifyTime < b . lastModifyTime ;
} ) ;
break ;
}
2026-04-23 20:56:11 +03:00
}
void FileSelectScreen : : main_display ( ) {
2026-04-18 20:51:45 +03:00
auto & gui = main . g . gui ;
2026-04-17 19:32:49 +03:00
CLAY_AUTO_ID ( {
2026-04-18 20:51:45 +03:00
. layout = {
2026-04-21 18:38:24 +03:00
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
2026-04-22 17:40:58 +03:00
. layoutDirection = CLAY_LEFT_TO_RIGHT
2026-04-18 20:51:45 +03:00
} ,
2026-04-17 19:32:49 +03:00
} ) {
2026-04-24 11:07:15 +03:00
mainMenuOpenAnim = gui . float_animation ( " main menu animation " , {
. duration = 0.1f ,
. easing = BezierEasing ( 0.36f , 0.0f , 0.64f , 1.0f )
} ) ;
actionBarOpenAnim = gui . float_animation ( " action bar animation " , {
. duration = 0.1f ,
. easing = BezierEasing ( 0.36f , 0.0f , 0.64f , 1.0f )
} ) ;
main_menu ( ) ;
CLAY_AUTO_ID ( {
. layout = {
2026-04-26 18:20:37 +03:00
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
2026-04-24 11:07:15 +03:00
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM ,
} ,
} ) {
2026-04-27 16:48:44 +03:00
if ( editMode ) {
numberOfSelectedEntries = std : : count_if ( fileList . begin ( ) , fileList . end ( ) , [ ] ( const FileInfo & f ) { return f . selected ; } ) ;
if ( numberOfSelectedEntries = = 0 )
actionBarOpenAnim - > animation_trigger_reverse ( ) ;
else
actionBarOpenAnim - > animation_trigger ( ) ;
edit_title_bar ( ) ;
}
else {
actionBarOpenAnim - > animation_trigger_reverse ( ) ;
title_bar ( ) ;
}
2026-04-22 17:40:58 +03:00
CLAY_AUTO_ID ( {
. layout = {
2026-04-24 11:07:15 +03:00
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
2026-04-27 16:48:44 +03:00
. layoutDirection = CLAY_LEFT_TO_RIGHT
2026-04-22 17:40:58 +03:00
} ,
} ) {
2026-04-29 18:53:53 +03:00
window_gap_side_bar ( gui , WindowFillSideBarConfig : : Direction : : LEFT ) ;
2026-04-27 00:42:25 +03:00
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
2026-04-27 16:48:44 +03:00
. 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 ,
2026-04-27 00:42:25 +03:00
} ,
} ) {
2026-04-27 16:48:44 +03:00
switch ( selectedMenu ) {
case SelectedMenu : : FILES :
2026-05-12 21:13:31 +03:00
file_view ( ) ;
2026-04-27 16:48:44 +03:00
if ( ! editMode )
create_file_button ( ) ;
break ;
case SelectedMenu : : TRASH :
2026-05-12 21:13:31 +03:00
file_view ( ) ;
2026-04-27 16:48:44 +03:00
break ;
2026-05-14 11:53:36 +03:00
case SelectedMenu : : CONNECT :
connect_view ( ) ;
break ;
2026-04-27 16:48:44 +03:00
case SelectedMenu : : SETTINGS :
2026-05-12 21:13:31 +03:00
settings_view ( ) ;
2026-04-27 16:48:44 +03:00
break ;
2026-05-17 17:32:05 +03:00
case SelectedMenu : : ABOUT :
about_view ( ) ;
break ;
2026-04-26 18:20:37 +03:00
}
2026-05-17 10:02:18 +03:00
global_log ( ) ;
2026-04-27 00:42:25 +03:00
}
2026-04-29 18:53:53 +03:00
window_gap_side_bar ( gui , WindowFillSideBarConfig : : Direction : : RIGHT ) ;
2026-04-22 17:40:58 +03:00
}
2026-05-17 17:32:05 +03:00
switch ( selectedMenu ) {
case SelectedMenu : : FILES :
window_gap_side_bar ( gui , WindowFillSideBarConfig : : Direction : : BOTTOM ) ;
edit_action_bar ( ) ;
break ;
case SelectedMenu : : TRASH :
window_gap_side_bar ( gui , WindowFillSideBarConfig : : Direction : : BOTTOM ) ;
edit_action_bar ( ) ;
break ;
case SelectedMenu : : CONNECT :
window_gap_side_bar ( gui , WindowFillSideBarConfig : : Direction : : BOTTOM ) ;
break ;
case SelectedMenu : : SETTINGS :
window_gap_side_bar ( gui , WindowFillSideBarConfig : : Direction : : BOTTOM ) ;
break ;
case SelectedMenu : : ABOUT :
about_bottom_bar ( ) ;
break ;
}
2026-04-27 16:48:44 +03:00
menu_black_box ( ) ;
2026-04-22 17:40:58 +03:00
}
}
}
2026-04-26 13:21:05 +03:00
void FileSelectScreen : : create_file_button ( ) {
auto & gui = main . g . gui ;
gui . set_z_index ( gui . get_z_index ( ) + 2 , [ & ] {
gui . element < LayoutElement > ( " create file button floating area " , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
CLAY ( lId , {
. layout = { . sizing = { . width = CLAY_SIZING_FIT ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } } ,
. floating = {
2026-05-13 19:56:14 +03:00
. offset = { - 25 , - 25 } ,
2026-04-26 13:21:05 +03:00
. zIndex = gui . get_z_index ( ) ,
. attachPoints = {
. element = CLAY_ATTACH_POINT_RIGHT_BOTTOM ,
. parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM
} ,
2026-04-27 00:42:25 +03:00
. attachTo = CLAY_ATTACH_TO_PARENT
2026-04-26 13:21:05 +03:00
}
} ) {
svg_icon_button ( gui , " add button " , " data/icons/plus.svg " , {
2026-05-13 19:56:14 +03:00
. drawType = SelectableButton : : DrawType : : FILLED_INVERSE ,
. size = 40.0f ,
2026-04-26 13:21:05 +03:00
. onClick = [ & ] {
2026-04-28 17:07:28 +03:00
CustomEvents : : emit_event < CustomEvents : : OpenInfiniPaintFileEvent > ( {
2026-04-27 16:48:44 +03:00
. isClient = false ,
2026-04-28 17:07:28 +03:00
. saveThumbnail = true ,
2026-04-27 16:48:44 +03:00
. filePathEmptyAutoSaveDir = savePath
2026-04-26 13:21:05 +03:00
} ) ;
}
} ) ;
}
} ) ;
} ) ;
}
2026-04-23 20:56:11 +03:00
void FileSelectScreen : : edit_action_bar_button ( const char * id , const std : : string & svgPath , const char * text , const std : : function < void ( ) > & onClick ) {
auto & gui = main . g . gui ;
CLAY_AUTO_ID ( {
. layout = { . sizing = { . width = CLAY_SIZING_FIXED ( 70 ) , . height = CLAY_SIZING_GROW ( 0 ) } }
} ) {
gui . element < SelectableButton > ( id , SelectableButton : : Data {
. drawType = SelectableButton : : DrawType : : TRANSPARENT_ALL ,
. onClick = onClick ,
. innerContent = [ & ] ( const SelectableButton : : InnerContentCallbackParameters & ) {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_FIT ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_TOP } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM ,
}
} ) {
CLAY_AUTO_ID ( {
. layout = { . sizing = { . width = CLAY_SIZING_FIXED ( 20 ) , . height = CLAY_SIZING_FIXED ( 20 ) } }
} ) {
gui . element < SVGIcon > ( " icon " , svgPath , false ) ;
}
text_label ( gui , text ) ;
}
}
} ) ;
}
}
void FileSelectScreen : : move_selected_files ( const std : : filesystem : : path & fromPath , const std : : filesystem : : path & toPath , TrashMoveType trashMoveType ) {
SDL_Time currentTime ;
SDL_GetCurrentTime ( & currentTime ) ;
std : : vector < std : : string > toFolderListNames ;
2026-04-27 16:48:44 +03:00
try {
toFolderListNames = glob_path_as_string_list ( toPath , " * " , 0 , [ & ] ( const auto & p ) { return p . stem ( ) . string ( ) ; } ) ;
} catch ( . . . ) {
2026-04-23 20:56:11 +03:00
SDL_CreateDirectory ( toPath . string ( ) . c_str ( ) ) ;
2026-04-27 16:48:44 +03:00
}
2026-04-23 20:56:11 +03:00
for ( const FileInfo & f : fileList ) {
if ( f . selected ) {
std : : string newFileName = ensure_string_unique ( toFolderListNames , f . fileName ) ;
2026-04-28 14:51:35 +03:00
toFolderListNames . emplace_back ( newFileName ) ;
2026-04-27 16:48:44 +03:00
std : : filesystem : : path filePath = fromPath / ( f . fileName + World : : DOT_FILE_EXTENSION ) ;
2026-04-23 20:56:11 +03:00
std : : filesystem : : path thumbnailPath = fromPath / ( f . fileName + " .jpg " ) ;
2026-04-27 16:48:44 +03:00
std : : filesystem : : path newFilePath = toPath / ( newFileName + World : : DOT_FILE_EXTENSION ) ;
2026-04-23 20:56:11 +03:00
std : : filesystem : : path newThumbnailPath = toPath / ( newFileName + " .jpg " ) ;
SDL_RenamePath ( filePath . string ( ) . c_str ( ) , newFilePath . string ( ) . c_str ( ) ) ;
SDL_RenamePath ( thumbnailPath . string ( ) . c_str ( ) , newThumbnailPath . string ( ) . c_str ( ) ) ;
switch ( trashMoveType ) {
case TrashMoveType : : NONE :
break ;
case TrashMoveType : : MOVE_TO_TRASH :
2026-05-13 14:31:35 +03:00
saveInfo . trash . files [ newFileName ] = TrashInfo : : TrashFile {
2026-04-23 20:56:11 +03:00
. trashTime = currentTime
} ;
break ;
case TrashMoveType : : MOVE_OUT_OF_TRASH :
2026-05-13 14:31:35 +03:00
saveInfo . trash . files . erase ( f . fileName ) ;
2026-04-23 20:56:11 +03:00
break ;
}
}
}
}
2026-05-31 18:35:57 +03:00
void FileSelectScreen : : share_selected_files ( ) {
# ifdef __ANDROID__
2026-05-31 23:00:12 +03:00
std : : vector < std : : string > filesToSend ;
for ( const FileInfo & f : fileList ) {
if ( f . selected )
filesToSend . emplace_back ( " saves/ " + f . fileName + " .infpnt " ) ;
2026-05-31 18:35:57 +03:00
}
2026-05-31 23:00:12 +03:00
if ( ! filesToSend . empty ( ) )
AndroidJNICalls : : shareInternalFiles ( filesToSend , " application/octet-stream " ) ;
# endif
2026-05-31 18:35:57 +03:00
}
2026-04-23 22:22:43 +03:00
void FileSelectScreen : : duplicate_selected_files ( const std : : filesystem : : path & inPath ) {
std : : vector < std : : string > toFolderListNames ;
2026-04-27 16:48:44 +03:00
try {
toFolderListNames = glob_path_as_string_list ( inPath , " * " , 0 , [ & ] ( const auto & p ) { return p . stem ( ) . string ( ) ; } ) ;
} catch ( . . . ) {
2026-04-23 22:22:43 +03:00
SDL_CreateDirectory ( inPath . string ( ) . c_str ( ) ) ;
2026-04-27 16:48:44 +03:00
}
2026-04-23 22:22:43 +03:00
for ( const FileInfo & f : fileList ) {
if ( f . selected ) {
std : : string newFileName = ensure_string_unique ( toFolderListNames , f . fileName ) ;
2026-04-28 14:51:35 +03:00
toFolderListNames . emplace_back ( newFileName ) ;
2026-04-23 22:22:43 +03:00
std : : filesystem : : path filePath = inPath / ( f . fileName + World : : DOT_FILE_EXTENSION ) ;
std : : filesystem : : path thumbnailPath = inPath / ( f . fileName + " .jpg " ) ;
2026-04-27 16:48:44 +03:00
std : : filesystem : : path newFilePath = inPath / ( newFileName + World : : DOT_FILE_EXTENSION ) ;
2026-04-23 22:22:43 +03:00
std : : filesystem : : path newThumbnailPath = inPath / ( newFileName + " .jpg " ) ;
SDL_CopyFile ( filePath . string ( ) . c_str ( ) , newFilePath . string ( ) . c_str ( ) ) ;
SDL_CopyFile ( thumbnailPath . string ( ) . c_str ( ) , newThumbnailPath . string ( ) . c_str ( ) ) ;
}
}
}
2026-04-23 20:56:11 +03:00
void FileSelectScreen : : delete_selected_files_in_trash ( ) {
for ( const FileInfo & f : fileList ) {
if ( f . selected ) {
std : : filesystem : : path filePath = trashPath / ( f . fileName + World : : DOT_FILE_EXTENSION ) ;
std : : filesystem : : path thumbnailPath = trashPath / ( f . fileName + " .jpg " ) ;
SDL_RemovePath ( filePath . string ( ) . c_str ( ) ) ;
SDL_RemovePath ( thumbnailPath . string ( ) . c_str ( ) ) ;
2026-05-13 14:31:35 +03:00
saveInfo . trash . files . erase ( f . fileName ) ;
2026-04-23 20:56:11 +03:00
}
}
}
2026-05-17 17:32:05 +03:00
void FileSelectScreen : : about_bottom_bar ( ) {
auto & gui = main . g . gui ;
gui . element < LayoutElement > ( " about bottom bar fill " , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
CLAY ( lId , { } ) {
window_fill_side_bar ( gui , {
. dir = WindowFillSideBarConfig : : Direction : : BOTTOM ,
. backgroundColor = gui . io . theme - > backColor1
} , [ & ] {
gui . element < LayoutElement > ( " about bottom bar " , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
CLAY ( lId , {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_FIXED ( 40 ) } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT
}
} ) {
gui . element < ScrollArea > ( " about scroll area " , ScrollArea : : Options {
. scrollHorizontal = true ,
. clipHorizontal = true ,
. scrollbarX = ScrollArea : : ScrollbarType : : NORMAL ,
. innerContent = [ & ] ( auto & ) {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT
} ,
} ) {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) }
}
} ) {
text_button ( gui , " infinipaintnoticebutton " , " InfiniPaint " , {
. drawType = SelectableButton : : DrawType : : TRANSPARENT_ALL ,
. isSelected = selectedLicense = = - 1 ,
. onClick = [ & ] { selectedLicense = - 1 ; }
} ) ;
}
for ( int i = 0 ; i < static_cast < int > ( main . conf . 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 " , main . conf . thirdPartyLicenses [ i ] . first , {
. drawType = SelectableButton : : DrawType : : TRANSPARENT_ALL ,
. isSelected = selectedLicense = = i ,
. onClick = [ & , i ] { selectedLicense = i ; }
} ) ;
} ) ;
}
}
}
}
} ) ;
}
} ) ;
} ) ;
}
} ) ;
}
2026-04-23 20:56:11 +03:00
void FileSelectScreen : : edit_action_bar ( ) {
auto & gui = main . g . gui ;
2026-04-24 11:07:15 +03:00
if ( actionBarOpenAnim - > get_val ( ) ) {
2026-05-17 10:02:18 +03:00
gui . set_z_index ( gui . get_z_index ( ) + 1000 , [ & ] {
2026-04-27 16:48:44 +03:00
CLAY_AUTO_ID ( {
2026-05-13 14:31:35 +03:00
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
. floating = {
. zIndex = gui . get_z_index ( ) ,
. attachPoints = {
. element = CLAY_ATTACH_POINT_LEFT_TOP ,
. parent = CLAY_ATTACH_POINT_LEFT_TOP ,
} ,
. attachTo = CLAY_ATTACH_TO_PARENT ,
}
} ) {
CLAY_AUTO_ID ( {
. layout = { . sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } } ,
} ) { }
gui . element < LayoutElement > ( " edit action bar bottom fill " , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
CLAY ( lId , { } ) {
window_fill_side_bar ( gui , {
. dir = WindowFillSideBarConfig : : Direction : : BOTTOM ,
. backgroundColor = gui . io . theme - > backColor1
} , [ & ] {
gui . element < LayoutElement > ( " edit action bar " , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
CLAY ( lId , {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_FIXED ( 50 * actionBarOpenAnim - > get_val ( ) ) } ,
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_CENTER } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT ,
2026-04-29 18:53:53 +03:00
}
2026-05-13 14:31:35 +03:00
} ) {
if ( actionBarOpenAnim - > is_at_end ( ) ) {
if ( selectedMenu = = SelectedMenu : : FILES ) {
edit_action_bar_button ( " trash " , " data/icons/trash.svg " , " Trash " , [ & ] {
move_selected_files ( savePath , trashPath , TrashMoveType : : MOVE_TO_TRASH ) ;
update_file_list ( fileList , savePath , false ) ;
editMode = false ;
} ) ;
edit_action_bar_button ( " duplicate " , " data/icons/RemixIcon/file-copy-line.svg " , " Duplicate " , [ & ] {
duplicate_selected_files ( savePath ) ;
update_file_list ( fileList , savePath , false ) ;
editMode = false ;
} ) ;
2026-05-31 18:35:57 +03:00
# ifdef __ANDROID__
2026-05-31 23:00:12 +03:00
edit_action_bar_button ( " share " , " data/icons/RemixIcon/share-line.svg " , " Share " , [ & ] {
share_selected_files ( ) ;
update_file_list ( fileList , savePath , false ) ;
editMode = false ;
} ) ;
2026-05-31 18:35:57 +03:00
# endif
2026-05-13 14:31:35 +03:00
}
else if ( selectedMenu = = SelectedMenu : : TRASH ) {
edit_action_bar_button ( " restore " , " data/icons/RemixIcon/refresh-line.svg " , " Restore " , [ & ] {
move_selected_files ( trashPath , savePath , TrashMoveType : : MOVE_OUT_OF_TRASH ) ;
update_file_list ( fileList , trashPath , true ) ;
editMode = false ;
} ) ;
edit_action_bar_button ( " delete " , " data/icons/trash.svg " , " Delete " , [ & ] {
delete_selected_files_in_trash ( ) ;
update_file_list ( fileList , trashPath , true ) ;
editMode = false ;
} ) ;
}
2026-04-29 18:53:53 +03:00
}
}
2026-05-13 14:31:35 +03:00
} ) ;
2026-04-29 18:53:53 +03:00
} ) ;
2026-05-13 14:31:35 +03:00
}
} ) ;
}
} ) ;
2026-04-24 11:07:15 +03:00
}
2026-04-23 20:56:11 +03:00
}
void FileSelectScreen : : edit_title_bar ( ) {
2026-04-22 17:40:58 +03:00
auto & gui = main . g . gui ;
2026-04-29 18:53:53 +03:00
gui . element < LayoutElement > ( " edit top toolbar " , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
CLAY ( lId , { } ) {
window_fill_side_bar ( gui , {
. dir = WindowFillSideBarConfig : : Direction : : TOP ,
. backgroundColor = gui . io . theme - > backColor0
} , [ & ] {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. padding = CLAY_PADDING_ALL ( gui . io . theme - > padding1 ) ,
. childGap = gui . io . theme - > childGap1 ,
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_CENTER } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT ,
}
} ) {
svg_icon_button ( gui , " close edit mode button " , " data/icons/close.svg " , {
. drawType = SelectableButton : : DrawType : : TRANSPARENT_ALL ,
. onClick = [ & ] {
editMode = false ;
}
} ) ;
mutable_text_label ( gui , " select files label " , ( numberOfSelectedEntries = = 0 ) ? " Select files " : ( std : : to_string ( numberOfSelectedEntries ) + " selected " ) ) ;
2026-04-27 00:42:25 +03:00
}
} ) ;
}
} ) ;
2026-04-23 20:56:11 +03:00
}
void FileSelectScreen : : title_bar ( ) {
auto & gui = main . g . gui ;
2026-04-29 18:53:53 +03:00
gui . element < LayoutElement > ( " top toolbar " , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
CLAY ( lId , { } ) {
window_fill_side_bar ( gui , {
. dir = WindowFillSideBarConfig : : Direction : : TOP ,
. backgroundColor = gui . io . theme - > backColor0
} , [ & ] {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
. padding = CLAY_PADDING_ALL ( gui . io . theme - > padding1 ) ,
. childGap = gui . io . theme - > childGap1 ,
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_CENTER } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT
2026-04-26 18:20:37 +03:00
}
2026-04-29 18:53:53 +03:00
} ) {
svg_icon_button ( gui , " main settings button " , " data/icons/menu.svg " , {
. drawType = SelectableButton : : DrawType : : TRANSPARENT_ALL ,
. onClick = [ & ] {
mainMenuOpenAnim - > animation_trigger ( ) ;
}
} ) ;
switch ( selectedMenu ) {
case SelectedMenu : : FILES :
2026-05-14 11:53:36 +03:00
mutable_text_label ( gui , " main screen header text files " , " Files " ) ;
2026-04-29 18:53:53 +03:00
break ;
case SelectedMenu : : TRASH :
2026-05-14 11:53:36 +03:00
mutable_text_label ( gui , " main screen header text trash " , " Trash " ) ;
break ;
case SelectedMenu : : CONNECT :
mutable_text_label ( gui , " main screen header text connect " , " Connect " ) ;
2026-04-29 18:53:53 +03:00
break ;
case SelectedMenu : : SETTINGS :
2026-05-14 11:53:36 +03:00
mutable_text_label ( gui , " main screen header text settings " , " Settings " ) ;
2026-04-29 18:53:53 +03:00
break ;
2026-05-17 17:32:05 +03:00
case SelectedMenu : : ABOUT :
mutable_text_label ( gui , " main screen header about " , " About " ) ;
break ;
2026-04-29 18:53:53 +03:00
}
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
}
} ) { }
if ( selectedMenu = = SelectedMenu : : FILES | | selectedMenu = = SelectedMenu : : TRASH ) {
SelectableButton * moreButton = svg_icon_button ( gui , " more settings button " , " data/icons/RemixIcon/more-2-fill.svg " , {
. drawType = SelectableButton : : DrawType : : TRANSPARENT_ALL ,
. onClick = [ & ] {
moreOptionsMenu = MoreOptionsMenu : : MAIN ;
2026-04-23 20:56:11 +03:00
}
2026-04-26 18:20:37 +03:00
} ) ;
2026-04-29 18:53:53 +03:00
if ( moreButton - > get_bb ( ) . has_value ( ) & & moreOptionsMenu ! = MoreOptionsMenu : : CLOSED ) {
2026-05-12 21:13:31 +03:00
size_t elemCount = 0 ;
2026-05-13 14:31:35 +03:00
switch ( moreOptionsMenu ) {
case MoreOptionsMenu : : CLOSED :
break ;
case MoreOptionsMenu : : MAIN :
elemCount = 3 ;
break ;
case MoreOptionsMenu : : VIEW :
elemCount = 4 ;
break ;
case MoreOptionsMenu : : SORT :
elemCount = 4 ;
break ;
}
2026-04-29 18:53:53 +03:00
gui . set_z_index ( gui . get_z_index ( ) + 10 , [ & ] {
2026-05-12 21:13:31 +03:00
dropdown_many_element_popup_layout ( gui , " more options popup " , {
. button = moreButton ,
. clickUpAwayCallback = [ & ] {
gui . set_post_callback_func_high_priority ( [ & ] { // Cancel actions on other buttons
moreOptionsMenu = MoreOptionsMenu : : CLOSED ;
main . g . gui . set_to_layout ( ) ;
} ) ;
} ,
. entrySize = { 200.0f , 30.0f } ,
. entryCount = elemCount ,
. entryLayout = [ & ] ( size_t i ) {
2026-05-13 14:31:35 +03:00
switch ( moreOptionsMenu ) {
case MoreOptionsMenu : : CLOSED :
break ;
case MoreOptionsMenu : : MAIN : {
switch ( i ) {
case 0 : {
text_transparent_option_button ( " Edit " , " Edit " , [ & ] {
moreOptionsMenu = MoreOptionsMenu : : CLOSED ;
start_edit_mode ( ) ;
} ) ;
break ;
}
case 1 : {
text_transparent_option_button ( " View " , " View " , [ & ] {
moreOptionsMenu = MoreOptionsMenu : : VIEW ;
} ) ;
break ;
}
case 2 : {
text_transparent_option_button ( " Sort " , " Sort " , [ & ] {
moreOptionsMenu = MoreOptionsMenu : : SORT ;
} ) ;
break ;
}
2026-05-12 21:13:31 +03:00
}
2026-05-13 14:31:35 +03:00
break ;
2026-05-12 21:13:31 +03:00
}
2026-05-13 14:31:35 +03:00
case MoreOptionsMenu : : VIEW : {
switch ( i ) {
case 0 : {
text_transparent_option_button ( " Large Grid " , " Large Grid " , [ & ] {
saveInfo . fileViewType = SaveInfo : : FileViewType : : LARGE_GRID ;
moreOptionsMenu = MoreOptionsMenu : : CLOSED ;
} ) ;
break ;
}
case 1 : {
text_transparent_option_button ( " Medium Grid " , " Medium Grid " , [ & ] {
saveInfo . fileViewType = SaveInfo : : FileViewType : : MEDIUM_GRID ;
moreOptionsMenu = MoreOptionsMenu : : CLOSED ;
} ) ;
break ;
}
case 2 : {
text_transparent_option_button ( " Small Grid " , " Small Grid " , [ & ] {
saveInfo . fileViewType = SaveInfo : : FileViewType : : SMALL_GRID ;
moreOptionsMenu = MoreOptionsMenu : : CLOSED ;
} ) ;
break ;
}
case 3 : {
text_transparent_option_button ( " List " , " List " , [ & ] {
saveInfo . fileViewType = SaveInfo : : FileViewType : : LIST ;
moreOptionsMenu = MoreOptionsMenu : : CLOSED ;
} ) ;
break ;
}
2026-05-12 21:13:31 +03:00
}
2026-05-13 14:31:35 +03:00
break ;
}
case MoreOptionsMenu : : SORT : {
switch ( i ) {
case 0 : {
text_transparent_option_button ( " Alphabetical Ascending " , " Alphabetical ↑ " , [ & ] {
saveInfo . fileSort = SaveInfo : : FileSort : : ALPHABETICAL_ASCENDING ;
sort_file_list ( fileList ) ;
} ) ;
break ;
}
case 1 : {
text_transparent_option_button ( " Alphabetical Descending " , " Alphabetical ↓ " , [ & ] {
saveInfo . fileSort = SaveInfo : : FileSort : : ALPHABETICAL_DESCENDING ;
sort_file_list ( fileList ) ;
} ) ;
break ;
}
case 2 : {
text_transparent_option_button ( " Time Ascending " , " Date modified ↑ " , [ & ] {
saveInfo . fileSort = SaveInfo : : FileSort : : TIME_ASCENDING ;
sort_file_list ( fileList ) ;
} ) ;
break ;
}
case 3 : {
text_transparent_option_button ( " Time Descending " , " Date modified ↓ " , [ & ] {
saveInfo . fileSort = SaveInfo : : FileSort : : TIME_DESCENDING ;
sort_file_list ( fileList ) ;
} ) ;
break ;
}
2026-05-12 21:13:31 +03:00
}
2026-05-13 14:31:35 +03:00
break ;
2026-05-12 21:13:31 +03:00
}
2026-04-29 18:53:53 +03:00
}
}
} ) ;
} ) ;
}
}
2026-04-26 18:20:37 +03:00
}
2026-04-29 18:53:53 +03:00
} ) ;
2026-04-23 20:56:11 +03:00
}
2026-04-26 18:20:37 +03:00
} ) ;
2026-04-22 17:40:58 +03:00
}
2026-04-23 20:56:11 +03:00
void FileSelectScreen : : text_transparent_option_button ( const char * id , const char * text , const std : : function < void ( ) > & onClick ) {
auto & gui = main . g . gui ;
text_button ( gui , id , text , {
. drawType = SelectableButton : : DrawType : : TRANSPARENT_ALL ,
. wide = true ,
2026-05-12 21:13:31 +03:00
. growHeight = true ,
2026-04-23 20:56:11 +03:00
. centered = false ,
. onClick = onClick
} ) ;
}
void FileSelectScreen : : icon_text_transparent_option_selected_button ( const char * id , const std : : string & svgPath , const char * text , bool isSelected , const std : : function < void ( ) > & onClick ) {
auto & gui = main . g . gui ;
text_button_with_icon ( gui , id , svgPath , text , {
. drawType = SelectableButton : : DrawType : : TRANSPARENT_ALL ,
. isSelected = isSelected ,
. wide = true ,
. centered = false ,
. onClick = onClick
} ) ;
}
2026-04-22 17:40:58 +03:00
void FileSelectScreen : : main_menu ( ) {
2026-04-24 11:07:15 +03:00
if ( ! mainMenuOpenAnim - > is_at_start ( ) ) {
auto & gui = main . g . gui ;
2026-04-29 18:53:53 +03:00
gui . element < LayoutElement > ( " main menu popup layout element " , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
CLAY ( lId , { } ) {
window_fill_side_bar ( gui , {
. dir = WindowFillSideBarConfig : : Direction : : LEFT ,
. backgroundColor = gui . io . theme - > backColor0
} , [ & ] {
gui . element < LayoutElement > ( " inner main menu element " , [ & ] ( LayoutElement * , const Clay_ElementId & lId ) {
CLAY ( lId , {
. layout = {
2026-05-12 21:13:31 +03:00
. sizing = { . width = CLAY_SIZING_FIXED ( 200.0f * mainMenuOpenAnim - > get_val ( ) ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
2026-04-29 18:53:53 +03:00
. padding = CLAY_PADDING_ALL ( gui . io . theme - > padding1 ) ,
. layoutDirection = CLAY_TOP_TO_BOTTOM ,
} ,
} ) {
if ( mainMenuOpenAnim - > is_at_end ( ) ) {
icon_text_transparent_option_selected_button ( " Files " , " data/icons/folder.svg " , " Files " , selectedMenu = = SelectedMenu : : FILES , [ & ] {
selectedMenu = SelectedMenu : : FILES ;
update_file_list ( fileList , savePath , false ) ;
mainMenuOpenAnim - > animation_trigger_reverse ( ) ;
2026-05-12 21:13:31 +03:00
if ( mainViewScrollArea ) mainViewScrollArea - > reset_scroll ( ) ;
2026-04-29 18:53:53 +03:00
} ) ;
icon_text_transparent_option_selected_button ( " Trash " , " data/icons/trash.svg " , " Trash " , selectedMenu = = SelectedMenu : : TRASH , [ & ] {
selectedMenu = SelectedMenu : : TRASH ;
update_file_list ( fileList , trashPath , true ) ;
mainMenuOpenAnim - > animation_trigger_reverse ( ) ;
2026-05-12 21:13:31 +03:00
if ( mainViewScrollArea ) mainViewScrollArea - > reset_scroll ( ) ;
2026-04-29 18:53:53 +03:00
} ) ;
2026-05-14 11:53:36 +03:00
icon_text_transparent_option_selected_button ( " Connect " , " data/icons/network.svg " , " Connect " , selectedMenu = = SelectedMenu : : CONNECT , [ & ] {
selectedMenu = SelectedMenu : : CONNECT ;
mainMenuOpenAnim - > animation_trigger_reverse ( ) ;
if ( mainViewScrollArea ) mainViewScrollArea - > reset_scroll ( ) ;
} ) ;
2026-04-29 18:53:53 +03:00
icon_text_transparent_option_selected_button ( " Settings " , " data/icons/RemixIcon/settings-3-line.svg " , " Settings " , selectedMenu = = SelectedMenu : : SETTINGS , [ & ] {
selectedMenu = SelectedMenu : : SETTINGS ;
mainMenuOpenAnim - > animation_trigger_reverse ( ) ;
2026-05-12 21:13:31 +03:00
if ( mainViewScrollArea ) mainViewScrollArea - > reset_scroll ( ) ;
2026-04-29 18:53:53 +03:00
} ) ;
2026-05-17 17:32:05 +03:00
icon_text_transparent_option_selected_button ( " About " , " data/icons/RemixIcon/question-line.svg " , " About " , selectedMenu = = SelectedMenu : : ABOUT , [ & ] {
selectedMenu = SelectedMenu : : ABOUT ;
mainMenuOpenAnim - > animation_trigger_reverse ( ) ;
if ( mainViewScrollArea ) mainViewScrollArea - > reset_scroll ( ) ;
} ) ;
icon_text_transparent_option_selected_button ( " Website " , " data/icons/RemixIcon/global-line.svg " , " Website " , false , [ & ] {
SDL_OpenURL ( " https://infinipaint.com/ " ) ;
} ) ;
icon_text_transparent_option_selected_button ( " Donate " , " data/icons/RemixIcon/hand-coin-line.svg " , " Donate " , false , [ & ] {
SDL_OpenURL ( " https://infinipaint.com/donate.html " ) ;
} ) ;
2026-04-29 18:53:53 +03:00
}
}
} ) ;
} ) ;
}
2026-04-24 11:07:15 +03:00
} ) ;
}
2026-04-22 17:40:58 +03:00
}
void FileSelectScreen : : file_view ( ) {
auto & gui = main . g . gui ;
2026-04-23 20:56:11 +03:00
std : : filesystem : : path folderPath = ( selectedMenu = = SelectedMenu : : TRASH ) ? trashPath : savePath ;
2026-04-23 21:49:13 +03:00
auto fileButton = [ & ] ( size_t i , bool isList , const Vector2f & iconSize ) {
2026-04-27 16:48:44 +03:00
std : : filesystem : : path filePath = folderPath / ( fileList [ i ] . fileName + World : : DOT_FILE_EXTENSION ) ;
2026-04-23 21:49:13 +03:00
gui . element < SelectableButton > ( " file button " , SelectableButton : : Data {
. isSelected = editMode & & fileList [ i ] . selected ,
. onClick = [ & , filePath , i ] {
2026-05-12 21:13:31 +03:00
if ( selectedMenu = = SelectedMenu : : TRASH ) {
if ( ! editMode )
start_edit_mode ( ) ;
2026-04-23 21:49:13 +03:00
fileList [ i ] . selected = ! fileList [ i ] . selected ;
2026-05-12 21:13:31 +03:00
}
2026-04-23 21:49:13 +03:00
else {
2026-05-12 21:13:31 +03:00
if ( editMode )
fileList [ i ] . selected = ! fileList [ i ] . selected ;
else {
CustomEvents : : emit_event < CustomEvents : : OpenInfiniPaintFileEvent > ( {
. isClient = false ,
. saveThumbnail = true ,
. filePathSource = filePath
} ) ;
}
2026-04-23 21:49:13 +03:00
}
} ,
. innerContent = [ & ] ( const SelectableButton : : InnerContentCallbackParameters & c ) {
if ( isList ) {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
. padding = CLAY_PADDING_ALL ( gui . io . theme - > padding1 ) ,
. childGap = 6 ,
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_CENTER } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT
}
} ) {
CLAY_AUTO_ID ( {
. layout = { . sizing = { . width = CLAY_SIZING_FIXED ( iconSize . x ( ) ) , . height = CLAY_SIZING_FIXED ( iconSize . y ( ) ) } } ,
} ) {
gui . element < ImageDisplay > ( " ico " , ImageDisplay : : Data {
. imgPath = folderPath / ( fileList [ i ] . fileName + " .jpg " ) ,
. radius = 20
} ) ;
}
CLAY_AUTO_ID ( {
. layout = {
2026-04-25 19:01:19 +03:00
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
2026-04-23 21:49:13 +03:00
. childGap = 6 ,
. childAlignment = { . x = CLAY_ALIGN_X_LEFT , . y = CLAY_ALIGN_Y_CENTER } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
}
} ) {
2026-05-13 14:31:35 +03:00
mutable_text_label ( gui , " file name label " , fileList [ i ] . fileName ) ;
mutable_text_label_light ( gui , " last modify date " , fileList [ i ] . lastModifyDate ) ;
2026-04-23 21:49:13 +03:00
}
2026-04-23 20:56:11 +03:00
}
2026-04-23 21:49:13 +03:00
}
else {
2026-04-22 17:40:58 +03:00
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
. padding = CLAY_PADDING_ALL ( gui . io . theme - > padding1 ) ,
. childGap = 6 ,
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_CENTER } ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
}
} ) {
CLAY_AUTO_ID ( {
2026-04-23 20:56:11 +03:00
. layout = { . sizing = { . width = CLAY_SIZING_FIXED ( iconSize . x ( ) ) , . height = CLAY_SIZING_FIXED ( iconSize . y ( ) ) } } ,
2026-04-22 17:40:58 +03:00
} ) {
gui . element < ImageDisplay > ( " ico " , ImageDisplay : : Data {
2026-04-23 20:56:11 +03:00
. imgPath = folderPath / ( fileList [ i ] . fileName + " .jpg " ) ,
2026-04-22 17:40:58 +03:00
. radius = 20
} ) ;
}
2026-04-25 19:01:19 +03:00
gui . element < LayoutElement > ( " file name layout elem " , [ & ] ( LayoutElement * l , const Clay_ElementId & lId ) {
CLAY ( lId , {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_FIT ( 0 ) } ,
. childAlignment = { . x = CLAY_ALIGN_X_CENTER , . y = CLAY_ALIGN_Y_TOP } ,
} ,
} ) {
if ( l - > get_bb ( ) ) {
RichText : : TextData fileNameText ;
fileNameText . paragraphs . emplace_back ( fileList [ i ] . fileName ) ;
gui . element < TextParagraph > ( " file name " , TextParagraph : : Data {
. text = fileNameText ,
. maxGrowX = l - > get_bb ( ) . value ( ) . width ( ) ,
. maxGrowY = 0.0f
} ) ;
}
}
} ) ;
2026-05-13 14:31:35 +03:00
mutable_text_label_light ( gui , " last modify date " , fileList [ i ] . lastModifyDate ) ;
2026-04-22 17:40:58 +03:00
}
}
2026-04-23 21:49:13 +03:00
}
} ) ;
} ;
2026-05-13 14:31:35 +03:00
if ( saveInfo . fileViewType = = SaveInfo : : FileViewType : : LIST ) {
2026-05-12 21:13:31 +03:00
mainViewScrollArea = gui . element < ManyElementScrollArea > ( " File selector list " , ManyElementScrollArea : : Options {
2026-04-23 21:49:13 +03:00
. entryHeight = 150.0f ,
. entryCount = fileList . size ( ) ,
. clipHorizontal = true ,
2026-04-27 16:48:44 +03:00
. xElementSize = CLAY_SIZING_GROW ( 0 ) ,
2026-04-23 21:49:13 +03:00
. elementContent = [ & ] ( size_t i ) {
fileButton ( i , true , Vector2f { 100.0f , 100.0f } ) ;
}
2026-04-30 14:45:36 +03:00
} ) - > scrollArea ;
2026-04-23 21:49:13 +03:00
}
else {
Vector2f entrySize { 0.0f , 0.0f } ;
Vector2f iconSize { 0.0f , 0.0f } ;
2026-05-13 14:31:35 +03:00
switch ( saveInfo . fileViewType ) {
case SaveInfo : : FileViewType : : LARGE_GRID :
2026-04-28 14:51:35 +03:00
entrySize = { 170.0f , 210.0f } ;
2026-04-27 17:20:16 +03:00
iconSize = { 130.0f , 130.0f } ;
2026-04-23 21:49:13 +03:00
break ;
2026-05-13 14:31:35 +03:00
case SaveInfo : : FileViewType : : MEDIUM_GRID :
2026-04-28 14:51:35 +03:00
entrySize = { 140.0f , 180.0f } ;
2026-04-27 17:20:16 +03:00
iconSize = { 100.0f , 100.0f } ;
2026-04-23 21:49:13 +03:00
break ;
2026-05-13 14:31:35 +03:00
case SaveInfo : : FileViewType : : SMALL_GRID :
2026-04-28 14:51:35 +03:00
entrySize = { 110.0f , 150.0f } ;
2026-04-27 17:20:16 +03:00
iconSize = { 70.0f , 70.0f } ;
2026-04-23 21:49:13 +03:00
break ;
default :
break ;
2026-04-22 17:40:58 +03:00
}
2026-05-12 21:13:31 +03:00
mainViewScrollArea = gui . element < GridScrollArea > ( " File selector grid " , GridScrollArea : : Options {
2026-04-23 21:49:13 +03:00
. entryWidth = entrySize . x ( ) ,
. childAlignmentX = CLAY_ALIGN_X_LEFT ,
. entryHeight = entrySize . y ( ) ,
. entryCount = fileList . size ( ) ,
2026-04-28 20:55:30 +03:00
. entryWidthIsMinimum = true ,
2026-04-23 21:49:13 +03:00
. elementContent = [ & ] ( size_t i ) {
fileButton ( i , false , iconSize ) ;
}
2026-04-30 14:45:36 +03:00
} ) - > scrollArea ;
2026-04-23 21:49:13 +03:00
}
2026-04-22 17:40:58 +03:00
}
2026-05-14 11:53:36 +03:00
void FileSelectScreen : : connect_view ( ) {
auto & gui = main . g . gui ;
mainViewScrollArea = gui . element < ScrollArea > ( " connect scroll area " , ScrollArea : : Options {
. scrollVertical = true ,
. clipVertical = true ,
. scrollbarY = ScrollArea : : ScrollbarType : : NORMAL ,
. innerContent = [ & ] ( auto & ) {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT
} ,
} ) {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 , 300 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
. childGap = gui . io . theme - > childGap1 ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
} ) {
input_text_field ( gui , " Connect lobby field " , " Lobby " , & connectLobbyStr ) ;
text_button ( gui , " Connect button " , " Connect " , {
. wide = true ,
. onClick = [ & ] {
CustomEvents : : emit_event < CustomEvents : : OpenInfiniPaintFileEvent > ( {
. isClient = true ,
. netSource = connectLobbyStr
} ) ;
}
} ) ;
text_button ( gui , " Paste and Connect button " , " Paste & Connect " , {
. wide = true ,
. onClick = [ & ] {
connectOnPaste = true ;
main . input . call_paste ( CustomEvents : : PasteEvent : : DataType : : TEXT , { . allowRichText = false } ) ;
}
} ) ;
}
}
}
} ) ;
}
2026-05-12 21:13:31 +03:00
void FileSelectScreen : : settings_view ( ) {
auto & gui = main . g . gui ;
mainViewScrollArea = gui . element < ScrollArea > ( " settings scroll area " , ScrollArea : : Options {
. scrollVertical = true ,
. clipVertical = true ,
. scrollbarY = ScrollArea : : ScrollbarType : : NORMAL ,
. innerContent = [ & ] ( auto & ) {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT
} ,
} ) {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 , 300 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
. childGap = gui . io . theme - > childGap1 ,
. layoutDirection = CLAY_TOP_TO_BOTTOM
} ,
} ) {
2026-05-14 11:53:36 +03:00
input_text_field ( gui , " display name input " , " Display name " , & main . conf . displayName ) ;
2026-05-12 21:13:31 +03:00
color_picker_button_field ( gui , " defaultCanvasBackgroundColor " , " Default canvas background color " , & main . conf . defaultCanvasBackgroundColor , { . hasAlpha = false } ) ;
input_scalar_field ( gui , " Max GUI Scale " , " Max GUI Scale " , & main . conf . guiScale , 1.0f , 2.0f , {
. decimalPrecision = 1 ,
. onEdit = [ & ] { main . g . window_update ( ) ; }
} ) ;
input_scalar_field ( gui , " jump transition time " , " Jump transition time " , & main . conf . jumpTransitionTime , 0.01f , 1000.0f , { . decimalPrecision = 2 } ) ;
2026-05-15 09:51:25 +03:00
checkbox_boolean_field ( gui , " disable touch when pen in proximity " , " Disable touch when pen in proximity " , & main . conf . tabletOptions . disableTouchWhenPenInProximity ) ;
2026-05-12 21:13:31 +03:00
checkbox_boolean_field ( gui , " make all tools share same size " , " Make all tools share size " , & main . toolConfig . globalConf . useGlobalRelativeWidth ) ;
2026-05-15 09:51:25 +03:00
slider_scalar_field ( gui , " tablet brush minimum size " , " Brush relative minimum size " , & main . conf . tabletOptions . brushMinimumSize , 0.0f , 1.0f , { . decimalPrecision = 3 } ) ;
2026-05-16 20:40:23 +03:00
slider_scalar_field ( gui , " tablet brush pressure smoothing factor " , " Brush pressure smoothing factor " , & main . conf . tabletOptions . brushPressureSmoothingFactor , 0.0f , 1.0f , { . decimalPrecision = 3 } ) ;
2026-05-15 09:51:25 +03:00
checkbox_boolean_field ( gui , " pen pressure width " , " Pen pressure affects brush size " , & main . conf . tabletOptions . pressureAffectsBrushWidth ) ;
2026-05-17 17:32:05 +03:00
# ifndef __ANDROID__
checkbox_boolean_field ( gui , " use mobile UI " , " Use mobile UI (requires restart) " , & main . conf . mobileUI ) ;
# endif
# ifndef __EMSCRIPTEN__
checkbox_boolean_field ( gui , " update notifications enable " , " Check for updates on startup " , & main . conf . checkForUpdates ) ;
# endif
2026-05-12 21:13:31 +03:00
}
}
}
} ) ;
}
2026-05-17 17:32:05 +03:00
void FileSelectScreen : : about_view ( ) {
auto & gui = main . g . gui ;
auto & io = gui . io ;
mainViewScrollArea = gui . element < ScrollArea > ( " about scroll area " , ScrollArea : : Options {
. scrollVertical = true ,
. clipVertical = true ,
. scrollbarY = ScrollArea : : ScrollbarType : : NORMAL ,
. innerContent = [ & ] ( auto & ) {
CLAY_AUTO_ID ( {
. layout = {
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
. layoutDirection = CLAY_LEFT_TO_RIGHT
} ,
} ) {
text_label_size ( gui , ( selectedLicense = = - 1 ) ? main . conf . ownLicenseText : main . conf . thirdPartyLicenses [ selectedLicense ] . second , 0.8f ) ;
}
}
} ) ;
}
2026-04-22 17:40:58 +03:00
void FileSelectScreen : : menu_black_box ( ) {
auto & gui = main . g . gui ;
2026-04-24 11:07:15 +03:00
if ( ! mainMenuOpenAnim - > is_at_start ( ) ) {
2026-05-17 17:32:05 +03:00
gui . set_z_index ( gui . get_z_index ( ) + 2000 , [ & ] {
2026-04-22 17:40:58 +03:00
gui . element < LayoutElement > ( " file list disable fill " , [ & ] ( LayoutElement * l , const Clay_ElementId & lId ) {
CLAY ( lId , {
2026-04-18 20:51:45 +03:00
. layout = {
2026-04-22 17:40:58 +03:00
. sizing = { . width = CLAY_SIZING_GROW ( 0 ) , . height = CLAY_SIZING_GROW ( 0 ) } ,
} ,
2026-04-24 11:07:15 +03:00
. backgroundColor = { 0.0f , 0.0f , 0.0f , 0.6f * mainMenuOpenAnim - > get_val ( ) } ,
2026-04-22 17:40:58 +03:00
. floating = {
. zIndex = gui . get_z_index ( ) ,
. attachPoints = {
. element = CLAY_ATTACH_POINT_LEFT_TOP ,
. parent = CLAY_ATTACH_POINT_LEFT_TOP
} ,
. attachTo = CLAY_ATTACH_TO_PARENT
2026-04-18 20:51:45 +03:00
}
} ) {
2026-04-22 17:40:58 +03:00
}
} , LayoutElement : : Callbacks {
2026-04-28 20:28:21 +03:00
. onClick = [ & ] ( LayoutElement * l , const InputManager : : MouseButtonCallbackArgs & m ) {
2026-04-22 17:40:58 +03:00
if ( m . down & & m . button = = InputManager : : MouseButton : : LEFT & & l - > mouseHovering ) {
2026-04-24 11:07:15 +03:00
mainMenuOpenAnim - > animation_trigger_reverse ( ) ;
2026-04-22 17:40:58 +03:00
gui . set_to_layout ( ) ;
2026-04-19 17:44:48 +03:00
}
2026-04-18 20:51:45 +03:00
}
2026-04-22 17:40:58 +03:00
} ) ;
2026-04-18 20:51:45 +03:00
} ) ;
2026-04-17 19:32:49 +03:00
}
}
2026-04-23 20:56:11 +03:00
void FileSelectScreen : : start_edit_mode ( ) {
for ( auto & f : fileList )
f . selected = false ;
editMode = true ;
2026-04-19 17:44:48 +03:00
}
2026-05-14 11:53:36 +03:00
void FileSelectScreen : : input_paste_callback ( const CustomEvents : : PasteEvent & paste ) {
if ( connectOnPaste & & paste . type = = CustomEvents : : PasteEvent : : DataType : : TEXT ) {
CustomEvents : : emit_event < CustomEvents : : OpenInfiniPaintFileEvent > ( {
. isClient = true ,
. netSource = paste . data
} ) ;
}
}
2026-04-22 21:40:01 +03:00
void FileSelectScreen : : input_open_infinipaint_file_callback ( const CustomEvents : : OpenInfiniPaintFileEvent & openFile ) {
main . create_new_tab ( openFile ) ;
2026-04-28 14:51:35 +03:00
main . set_screen ( [ & ] ( std : : unique_ptr < Screen > ) { return std : : make_unique < PhoneDrawingProgramScreen > ( main ) ; } ) ;
2026-04-22 21:40:01 +03:00
}
2026-04-27 17:20:16 +03:00
void FileSelectScreen : : input_global_back_button_callback ( ) {
if ( ! mainMenuOpenAnim - > is_at_start ( ) )
mainMenuOpenAnim - > animation_trigger_reverse ( ) ;
else if ( editMode )
editMode = false ;
2026-05-17 22:18:53 +03:00
else if ( selectedMenu ! = SelectedMenu : : FILES ) {
selectedMenu = SelectedMenu : : FILES ;
update_file_list ( fileList , savePath , false ) ;
mainMenuOpenAnim - > animation_trigger_reverse ( ) ;
if ( mainViewScrollArea ) mainViewScrollArea - > reset_scroll ( ) ;
}
2026-04-27 21:30:31 +03:00
else
main . setToQuit = true ;
2026-04-27 17:20:16 +03:00
main . g . gui . set_to_layout ( ) ;
}
2026-04-17 19:32:49 +03:00
void FileSelectScreen : : draw ( SkCanvas * canvas ) {
2026-04-24 01:28:38 +03:00
canvas - > clear ( main . g . gui . io . theme - > backColor0 ) ;
2026-04-17 19:32:49 +03:00
}
2026-04-23 20:56:11 +03:00
2026-04-28 14:51:35 +03:00
void FileSelectScreen : : save_files ( ) {
2026-04-23 20:56:11 +03:00
nlohmann : : json j ;
2026-05-13 14:31:35 +03:00
nlohmann : : to_json ( j , saveInfo ) ;
2026-04-23 20:56:11 +03:00
std : : string saveJson = nlohmann : : to_string ( j ) ;
2026-05-13 14:31:35 +03:00
SDL_SaveFile ( infoPath . string ( ) . c_str ( ) , saveJson . c_str ( ) , saveJson . size ( ) ) ;
2026-04-23 20:56:11 +03:00
}
2026-04-28 14:51:35 +03:00
2026-05-18 09:45:17 +03:00
void FileSelectScreen : : input_app_about_to_go_to_background_callback ( ) {
2026-04-28 14:51:35 +03:00
save_files ( ) ;
}
FileSelectScreen : : ~ FileSelectScreen ( ) {
# ifndef __ANDROID__
save_files ( ) ;
# endif
}