2014-02-10 02:10:30 +01:00
/**************************************************************************/
/* project_manager.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
2018-01-05 00:50:27 +01:00
2014-02-10 02:10:30 +01:00
# include "project_manager.h"
2017-01-16 08:04:19 +01:00
2022-02-12 02:46:22 +01:00
# include "core/config/project_settings.h"
2018-09-11 18:13:45 +02:00
# include "core/io/config_file.h"
2021-06-11 14:51:48 +02:00
# include "core/io/dir_access.h"
# include "core/io/file_access.h"
2018-09-11 18:13:45 +02:00
# include "core/io/resource_saver.h"
2022-09-07 00:46:12 +02:00
# include "core/io/stream_peer_tls.h"
2018-09-11 18:13:45 +02:00
# include "core/io/zip_io.h"
# include "core/os/keyboard.h"
# include "core/os/os.h"
2020-11-07 23:33:38 +01:00
# include "core/string/translation.h"
2018-09-11 18:13:45 +02:00
# include "core/version.h"
2022-02-24 02:19:27 +01:00
# include "editor/editor_paths.h"
2022-02-12 02:46:22 +01:00
# include "editor/editor_scale.h"
# include "editor/editor_settings.h"
2023-08-13 02:33:39 +02:00
# include "editor/editor_string_names.h"
2022-02-12 02:46:22 +01:00
# include "editor/editor_themes.h"
2020-09-29 21:01:26 +02:00
# include "editor/editor_vcs_interface.h"
2023-04-07 18:59:49 +02:00
# include "editor/gui/editor_file_dialog.h"
2023-03-10 13:57:32 +01:00
# include "editor/plugins/asset_library_editor_plugin.h"
2022-08-13 21:52:03 +02:00
# include "main/main.h"
2014-02-10 02:10:30 +01:00
# include "scene/gui/center_container.h"
2022-11-19 12:45:49 +01:00
# include "scene/gui/check_box.h"
2023-03-17 23:30:21 +01:00
# include "scene/gui/color_rect.h"
# include "scene/gui/flow_container.h"
2014-02-10 02:10:30 +01:00
# include "scene/gui/line_edit.h"
# include "scene/gui/margin_container.h"
# include "scene/gui/panel_container.h"
2016-06-08 14:25:47 +02:00
# include "scene/gui/separator.h"
2017-01-14 10:52:54 +01:00
# include "scene/gui/texture_rect.h"
2020-03-04 17:36:09 +01:00
# include "scene/main/window.h"
2023-07-11 22:29:09 +02:00
# include "scene/resources/image_texture.h"
2020-03-03 14:36:29 +01:00
# include "servers/display_server.h"
2021-10-30 16:15:00 +02:00
# include "servers/navigation_server_3d.h"
2022-02-12 02:46:22 +01:00
# include "servers/physics_server_2d.h"
2016-07-12 02:34:02 +02:00
2022-08-26 15:22:30 +02:00
constexpr int GODOT4_CONFIG_VERSION = 5 ;
2023-03-10 13:57:32 +01:00
/// Project Dialog.
2017-12-27 00:39:46 +01:00
2023-03-10 13:57:32 +01:00
void ProjectDialog : : _set_message ( const String & p_msg , MessageType p_type , InputType input_type ) {
msg - > set_text ( p_msg ) ;
Ref < Texture2D > current_path_icon = status_rect - > get_texture ( ) ;
Ref < Texture2D > current_install_icon = install_status_rect - > get_texture ( ) ;
Ref < Texture2D > new_icon ;
2017-12-27 00:39:46 +01:00
2023-03-10 13:57:32 +01:00
switch ( p_type ) {
case MESSAGE_ERROR : {
2023-08-13 02:33:39 +02:00
msg - > add_theme_color_override ( " font_color " , get_theme_color ( SNAME ( " error_color " ) , EditorStringName ( Editor ) ) ) ;
2023-03-10 13:57:32 +01:00
msg - > set_modulate ( Color ( 1 , 1 , 1 , 1 ) ) ;
2023-08-13 02:33:39 +02:00
new_icon = get_editor_theme_icon ( SNAME ( " StatusError " ) ) ;
2017-12-27 00:39:46 +01:00
2023-03-10 13:57:32 +01:00
} break ;
case MESSAGE_WARNING : {
2023-08-13 02:33:39 +02:00
msg - > add_theme_color_override ( " font_color " , get_theme_color ( SNAME ( " warning_color " ) , EditorStringName ( Editor ) ) ) ;
2023-03-10 13:57:32 +01:00
msg - > set_modulate ( Color ( 1 , 1 , 1 , 1 ) ) ;
2023-08-13 02:33:39 +02:00
new_icon = get_editor_theme_icon ( SNAME ( " StatusWarning " ) ) ;
2017-12-27 00:39:46 +01:00
2023-03-10 13:57:32 +01:00
} break ;
case MESSAGE_SUCCESS : {
2023-03-10 15:22:33 +01:00
msg - > remove_theme_color_override ( " font_color " ) ;
2023-03-10 13:57:32 +01:00
msg - > set_modulate ( Color ( 1 , 1 , 1 , 0 ) ) ;
2023-08-13 02:33:39 +02:00
new_icon = get_editor_theme_icon ( SNAME ( " StatusSuccess " ) ) ;
2018-01-17 08:45:09 +01:00
2023-03-10 13:57:32 +01:00
} break ;
2017-08-31 03:08:17 +02:00
}
2014-02-10 02:10:30 +01:00
2023-03-10 13:57:32 +01:00
if ( current_path_icon ! = new_icon & & input_type = = PROJECT_PATH ) {
status_rect - > set_texture ( new_icon ) ;
} else if ( current_install_icon ! = new_icon & & input_type = = INSTALL_PATH ) {
install_status_rect - > set_texture ( new_icon ) ;
}
}
2023-08-16 14:00:01 +02:00
static bool is_zip_file ( Ref < DirAccess > p_d , const String & p_path ) {
return p_path . ends_with ( " .zip " ) & & p_d - > file_exists ( p_path ) ;
}
2023-03-10 13:57:32 +01:00
String ProjectDialog : : _test_path ( ) {
Ref < DirAccess > d = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
2023-08-16 14:00:01 +02:00
const String base_path = project_path - > get_text ( ) ;
2023-03-10 13:57:32 +01:00
String valid_path , valid_install_path ;
2023-08-16 14:00:01 +02:00
bool is_zip = false ;
if ( d - > change_dir ( base_path ) = = OK ) {
valid_path = base_path ;
} else if ( is_zip_file ( d , base_path ) ) {
valid_path = base_path ;
is_zip = true ;
} else if ( d - > change_dir ( base_path . strip_edges ( ) ) = = OK ) {
valid_path = base_path . strip_edges ( ) ;
} else if ( is_zip_file ( d , base_path . strip_edges ( ) ) ) {
valid_path = base_path . strip_edges ( ) ;
is_zip = true ;
2023-03-10 13:57:32 +01:00
}
if ( valid_path . is_empty ( ) ) {
_set_message ( TTR ( " The path specified doesn't exist. " ) , MESSAGE_ERROR ) ;
get_ok_button ( ) - > set_disabled ( true ) ;
return " " ;
}
2023-08-16 14:00:01 +02:00
if ( mode = = MODE_IMPORT & & is_zip ) {
2023-03-10 13:57:32 +01:00
if ( d - > change_dir ( install_path - > get_text ( ) ) = = OK ) {
valid_install_path = install_path - > get_text ( ) ;
} else if ( d - > change_dir ( install_path - > get_text ( ) . strip_edges ( ) ) = = OK ) {
valid_install_path = install_path - > get_text ( ) . strip_edges ( ) ;
2016-11-14 13:40:05 +01:00
}
2023-03-10 13:57:32 +01:00
if ( valid_install_path . is_empty ( ) ) {
2023-08-16 14:00:01 +02:00
_set_message ( TTR ( " The install path specified doesn't exist. " ) , MESSAGE_ERROR , INSTALL_PATH ) ;
2020-12-14 19:37:30 +01:00
get_ok_button ( ) - > set_disabled ( true ) ;
2016-11-14 13:40:05 +01:00
return " " ;
2014-02-10 02:10:30 +01:00
}
2023-03-10 13:57:32 +01:00
}
2014-02-10 02:10:30 +01:00
2023-03-10 13:57:32 +01:00
if ( mode = = MODE_IMPORT | | mode = = MODE_RENAME ) {
2023-08-16 14:00:01 +02:00
if ( ! d - > file_exists ( " project.godot " ) ) {
if ( is_zip ) {
2023-03-10 13:57:32 +01:00
Ref < FileAccess > io_fa ;
zlib_filefunc_def io = zipio_create_io ( & io_fa ) ;
2018-06-21 06:17:52 +02:00
2023-03-10 13:57:32 +01:00
unzFile pkg = unzOpen2 ( valid_path . utf8 ( ) . get_data ( ) , & io ) ;
if ( ! pkg ) {
_set_message ( TTR ( " Error opening package file (it's not in ZIP format). " ) , MESSAGE_ERROR ) ;
get_ok_button ( ) - > set_disabled ( true ) ;
unzClose ( pkg ) ;
return " " ;
}
2018-06-21 06:17:52 +02:00
2023-03-10 13:57:32 +01:00
int ret = unzGoToFirstFile ( pkg ) ;
while ( ret = = UNZ_OK ) {
unz_file_info info ;
char fname [ 16384 ] ;
ret = unzGetCurrentFileInfo ( pkg , & info , fname , 16384 , nullptr , 0 , nullptr , 0 ) ;
if ( ret ! = UNZ_OK ) {
break ;
2018-06-21 06:17:52 +02:00
}
2023-03-10 13:57:32 +01:00
if ( String : : utf8 ( fname ) . ends_with ( " project.godot " ) ) {
break ;
2018-06-21 06:17:52 +02:00
}
2023-03-10 13:57:32 +01:00
ret = unzGoToNextFile ( pkg ) ;
}
2018-06-21 06:17:52 +02:00
2023-03-10 13:57:32 +01:00
if ( ret = = UNZ_END_OF_LIST_OF_FILE ) {
_set_message ( TTR ( " Invalid \" .zip \" project file; it doesn't contain a \" project.godot \" file. " ) , MESSAGE_ERROR ) ;
get_ok_button ( ) - > set_disabled ( true ) ;
2018-06-21 06:17:52 +02:00
unzClose ( pkg ) ;
2023-03-10 13:57:32 +01:00
return " " ;
}
2018-06-21 06:17:52 +02:00
2023-03-10 13:57:32 +01:00
unzClose ( pkg ) ;
// check if the specified install folder is empty, even though this is not an error, it is good to check here
d - > list_dir_begin ( ) ;
is_folder_empty = true ;
String n = d - > get_next ( ) ;
while ( ! n . is_empty ( ) ) {
if ( ! n . begins_with ( " . " ) ) {
// Allow `.`, `..` (reserved current/parent folder names)
// and hidden files/folders to be present.
// For instance, this lets users initialize a Git repository
// and still be able to create a project in the directory afterwards.
is_folder_empty = false ;
break ;
2018-06-21 06:17:52 +02:00
}
2023-03-10 13:57:32 +01:00
n = d - > get_next ( ) ;
}
d - > list_dir_end ( ) ;
2018-06-21 06:17:52 +02:00
2023-03-10 13:57:32 +01:00
if ( ! is_folder_empty ) {
2023-08-16 14:00:01 +02:00
_set_message ( TTR ( " Please choose an empty install folder. " ) , MESSAGE_WARNING , INSTALL_PATH ) ;
2020-12-14 19:37:30 +01:00
get_ok_button ( ) - > set_disabled ( true ) ;
2018-06-21 06:17:52 +02:00
return " " ;
}
2023-03-10 13:57:32 +01:00
} else {
2023-07-30 12:09:54 +02:00
_set_message ( TTR ( " Please choose a \" project.godot \" , a directory with it, or a \" .zip \" file. " ) , MESSAGE_ERROR ) ;
2023-03-10 13:57:32 +01:00
install_path_container - > hide ( ) ;
2020-12-14 19:37:30 +01:00
get_ok_button ( ) - > set_disabled ( true ) ;
2016-11-14 13:40:05 +01:00
return " " ;
2014-02-10 02:10:30 +01:00
}
2023-08-16 14:00:01 +02:00
} else if ( is_zip ) {
_set_message ( TTR ( " The install directory already contains a Godot project. " ) , MESSAGE_ERROR , INSTALL_PATH ) ;
2023-03-10 13:57:32 +01:00
get_ok_button ( ) - > set_disabled ( true ) ;
return " " ;
}
2023-03-15 23:48:52 +01:00
2023-03-10 13:57:32 +01:00
} else {
// Check if the specified folder is empty, even though this is not an error, it is good to check here.
d - > list_dir_begin ( ) ;
is_folder_empty = true ;
String n = d - > get_next ( ) ;
while ( ! n . is_empty ( ) ) {
if ( ! n . begins_with ( " . " ) ) {
// Allow `.`, `..` (reserved current/parent folder names)
// and hidden files/folders to be present.
// For instance, this lets users initialize a Git repository
// and still be able to create a project in the directory afterwards.
is_folder_empty = false ;
break ;
2017-08-31 03:08:17 +02:00
}
2023-03-10 13:57:32 +01:00
n = d - > get_next ( ) ;
2014-02-10 02:10:30 +01:00
}
2023-03-10 13:57:32 +01:00
d - > list_dir_end ( ) ;
2014-02-10 02:10:30 +01:00
2023-03-10 13:57:32 +01:00
if ( ! is_folder_empty ) {
if ( valid_path = = OS : : get_singleton ( ) - > get_environment ( " HOME " ) | | valid_path = = OS : : get_singleton ( ) - > get_system_dir ( OS : : SYSTEM_DIR_DOCUMENTS ) | | valid_path = = OS : : get_singleton ( ) - > get_executable_path ( ) . get_base_dir ( ) ) {
_set_message ( TTR ( " You cannot save a project in the selected path. Please make a new folder or choose a new path. " ) , MESSAGE_ERROR ) ;
get_ok_button ( ) - > set_disabled ( true ) ;
return " " ;
}
_set_message ( TTR ( " The selected path is not empty. Choosing an empty folder is highly recommended. " ) , MESSAGE_WARNING ) ;
get_ok_button ( ) - > set_disabled ( false ) ;
return valid_path ;
}
2014-02-10 02:10:30 +01:00
}
2023-03-10 13:57:32 +01:00
_set_message ( " " ) ;
_set_message ( " " , MESSAGE_SUCCESS , INSTALL_PATH ) ;
get_ok_button ( ) - > set_disabled ( false ) ;
return valid_path ;
}
2014-02-10 02:10:30 +01:00
2023-08-16 14:00:01 +02:00
void ProjectDialog : : _update_path ( const String & p_path ) {
2023-03-10 13:57:32 +01:00
String sp = _test_path ( ) ;
if ( ! sp . is_empty ( ) ) {
// If the project name is empty or default, infer the project name from the selected folder name
if ( project_name - > get_text ( ) . strip_edges ( ) . is_empty ( ) | | project_name - > get_text ( ) . strip_edges ( ) = = TTR ( " New Game Project " ) ) {
sp = sp . replace ( " \\ " , " / " ) ;
int lidx = sp . rfind ( " / " ) ;
2017-08-31 03:08:17 +02:00
2023-03-10 13:57:32 +01:00
if ( lidx ! = - 1 ) {
sp = sp . substr ( lidx + 1 , sp . length ( ) ) . capitalize ( ) ;
}
if ( sp . is_empty ( ) & & mode = = MODE_IMPORT ) {
sp = TTR ( " Imported Project " ) ;
2014-02-10 02:10:30 +01:00
}
2023-03-10 13:57:32 +01:00
project_name - > set_text ( sp ) ;
_text_changed ( sp ) ;
2014-02-10 02:10:30 +01:00
}
}
2023-03-10 13:57:32 +01:00
if ( ! created_folder_path . is_empty ( ) & & created_folder_path ! = p_path ) {
_remove_created_folder ( ) ;
}
}
2020-08-27 15:43:49 +02:00
2023-08-16 14:00:01 +02:00
void ProjectDialog : : _path_text_changed ( const String & p_path ) {
Ref < DirAccess > d = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
if ( mode = = MODE_IMPORT & & is_zip_file ( d , p_path ) ) {
install_path - > set_text ( p_path . get_base_dir ( ) ) ;
install_path_container - > show ( ) ;
} else if ( mode = = MODE_IMPORT & & is_zip_file ( d , p_path . strip_edges ( ) ) ) {
install_path - > set_text ( p_path . strip_edges ( ) . get_base_dir ( ) ) ;
install_path_container - > show ( ) ;
} else {
install_path_container - > hide ( ) ;
}
_update_path ( p_path . simplify_path ( ) ) ;
}
2023-03-10 13:57:32 +01:00
void ProjectDialog : : _file_selected ( const String & p_path ) {
2023-07-30 12:09:54 +02:00
// If not already shown.
show_dialog ( ) ;
2023-03-10 13:57:32 +01:00
String p = p_path ;
if ( mode = = MODE_IMPORT ) {
if ( p . ends_with ( " project.godot " ) ) {
p = p . get_base_dir ( ) ;
install_path_container - > hide ( ) ;
get_ok_button ( ) - > set_disabled ( false ) ;
} else if ( p . ends_with ( " .zip " ) ) {
install_path - > set_text ( p . get_base_dir ( ) ) ;
install_path_container - > show ( ) ;
get_ok_button ( ) - > set_disabled ( false ) ;
2018-06-21 06:17:52 +02:00
} else {
2023-03-10 13:57:32 +01:00
_set_message ( TTR ( " Please choose a \" project.godot \" or \" .zip \" file. " ) , MESSAGE_ERROR ) ;
get_ok_button ( ) - > set_disabled ( true ) ;
return ;
2018-06-21 06:17:52 +02:00
}
2014-02-10 02:10:30 +01:00
}
2023-03-10 13:57:32 +01:00
String sp = p . simplify_path ( ) ;
project_path - > set_text ( sp ) ;
2023-08-16 14:00:01 +02:00
_update_path ( sp ) ;
2023-03-10 13:57:32 +01:00
if ( p . ends_with ( " .zip " ) ) {
2023-12-18 15:46:56 +01:00
callable_mp ( ( Control * ) install_path , & Control : : grab_focus ) . call_deferred ( ) ;
2023-03-10 13:57:32 +01:00
} else {
2023-12-18 15:46:56 +01:00
callable_mp ( ( Control * ) get_ok_button ( ) , & Control : : grab_focus ) . call_deferred ( ) ;
2014-02-10 02:10:30 +01:00
}
2023-03-10 13:57:32 +01:00
}
2014-02-10 02:10:30 +01:00
2023-03-10 13:57:32 +01:00
void ProjectDialog : : _path_selected ( const String & p_path ) {
2023-07-30 12:09:54 +02:00
// If not already shown.
show_dialog ( ) ;
2023-03-10 13:57:32 +01:00
String sp = p_path . simplify_path ( ) ;
project_path - > set_text ( sp ) ;
2023-08-16 14:00:01 +02:00
_update_path ( sp ) ;
2023-12-18 15:46:56 +01:00
callable_mp ( ( Control * ) get_ok_button ( ) , & Control : : grab_focus ) . call_deferred ( ) ;
2023-03-10 13:57:32 +01:00
}
2018-06-21 06:17:52 +02:00
2023-03-10 13:57:32 +01:00
void ProjectDialog : : _install_path_selected ( const String & p_path ) {
String sp = p_path . simplify_path ( ) ;
install_path - > set_text ( sp ) ;
2023-08-16 14:00:01 +02:00
_update_path ( sp ) ;
2023-12-18 15:46:56 +01:00
callable_mp ( ( Control * ) get_ok_button ( ) , & Control : : grab_focus ) . call_deferred ( ) ;
2023-03-10 13:57:32 +01:00
}
2017-08-31 03:08:17 +02:00
2023-03-10 13:57:32 +01:00
void ProjectDialog : : _browse_path ( ) {
fdialog - > set_current_dir ( project_path - > get_text ( ) ) ;
2014-02-10 02:10:30 +01:00
2023-03-10 13:57:32 +01:00
if ( mode = = MODE_IMPORT ) {
2023-07-30 12:09:54 +02:00
fdialog - > set_file_mode ( EditorFileDialog : : FILE_MODE_OPEN_ANY ) ;
2023-03-10 13:57:32 +01:00
fdialog - > clear_filters ( ) ;
fdialog - > add_filter ( " project.godot " , vformat ( " %s %s " , VERSION_NAME , TTR ( " Project " ) ) ) ;
fdialog - > add_filter ( " *.zip " , TTR ( " ZIP File " ) ) ;
} else {
fdialog - > set_file_mode ( EditorFileDialog : : FILE_MODE_OPEN_DIR ) ;
2018-06-21 06:17:52 +02:00
}
2023-03-10 13:57:32 +01:00
fdialog - > popup_file_dialog ( ) ;
}
2018-06-21 06:17:52 +02:00
2023-03-10 13:57:32 +01:00
void ProjectDialog : : _browse_install_path ( ) {
fdialog_install - > set_current_dir ( install_path - > get_text ( ) ) ;
fdialog_install - > set_file_mode ( EditorFileDialog : : FILE_MODE_OPEN_DIR ) ;
fdialog_install - > popup_file_dialog ( ) ;
}
2017-08-31 03:08:17 +02:00
2023-03-10 13:57:32 +01:00
void ProjectDialog : : _create_folder ( ) {
const String project_name_no_edges = project_name - > get_text ( ) . strip_edges ( ) ;
if ( project_name_no_edges . is_empty ( ) | | ! created_folder_path . is_empty ( ) | | project_name_no_edges . ends_with ( " . " ) ) {
_set_message ( TTR ( " Invalid project name. " ) , MESSAGE_WARNING ) ;
return ;
}
Ref < DirAccess > d = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
if ( d - > change_dir ( project_path - > get_text ( ) ) = = OK ) {
if ( ! d - > dir_exists ( project_name_no_edges ) ) {
if ( d - > make_dir ( project_name_no_edges ) = = OK ) {
d - > change_dir ( project_name_no_edges ) ;
String dir_str = d - > get_current_dir ( ) ;
project_path - > set_text ( dir_str ) ;
2023-08-16 14:00:01 +02:00
_update_path ( dir_str ) ;
2023-03-10 13:57:32 +01:00
created_folder_path = d - > get_current_dir ( ) ;
create_dir - > set_disabled ( true ) ;
2017-12-27 00:39:46 +01:00
} else {
2023-03-10 13:57:32 +01:00
dialog_error - > set_text ( TTR ( " Couldn't create folder. " ) ) ;
2020-03-06 18:00:16 +01:00
dialog_error - > popup_centered ( ) ;
2017-08-31 03:08:17 +02:00
}
2023-03-10 13:57:32 +01:00
} else {
dialog_error - > set_text ( TTR ( " There is already a folder in this path with the specified name. " ) ) ;
dialog_error - > popup_centered ( ) ;
2017-08-31 03:08:17 +02:00
}
2017-09-07 02:04:41 +02:00
}
2023-03-10 13:57:32 +01:00
}
2017-09-07 02:04:41 +02:00
2023-03-10 13:57:32 +01:00
void ProjectDialog : : _text_changed ( const String & p_text ) {
if ( mode ! = MODE_NEW ) {
return ;
}
2017-08-31 03:08:17 +02:00
2023-03-10 13:57:32 +01:00
_test_path ( ) ;
2017-08-31 03:08:17 +02:00
2023-03-10 13:57:32 +01:00
if ( p_text . strip_edges ( ) . is_empty ( ) ) {
_set_message ( TTR ( " It would be a good idea to name your project. " ) , MESSAGE_ERROR ) ;
2014-02-10 02:10:30 +01:00
}
2023-03-10 13:57:32 +01:00
}
2014-02-10 02:10:30 +01:00
2023-03-10 13:57:32 +01:00
void ProjectDialog : : _nonempty_confirmation_ok_pressed ( ) {
is_folder_empty = true ;
ok_pressed ( ) ;
}
void ProjectDialog : : _renderer_selected ( ) {
2023-09-28 11:40:18 +02:00
ERR_FAIL_NULL ( renderer_button_group - > get_pressed_button ( ) ) ;
2023-04-22 01:51:57 +02:00
2023-03-10 13:57:32 +01:00
String renderer_type = renderer_button_group - > get_pressed_button ( ) - > get_meta ( SNAME ( " rendering_method " ) ) ;
if ( renderer_type = = " forward_plus " ) {
renderer_info - > set_text (
String : : utf8 ( " • " ) + TTR ( " Supports desktop platforms only. " ) +
String : : utf8 ( " \n • " ) + TTR ( " Advanced 3D graphics available. " ) +
String : : utf8 ( " \n • " ) + TTR ( " Can scale to large complex scenes. " ) +
String : : utf8 ( " \n • " ) + TTR ( " Uses RenderingDevice backend. " ) +
String : : utf8 ( " \n • " ) + TTR ( " Slower rendering of simple scenes. " ) ) ;
} else if ( renderer_type = = " mobile " ) {
renderer_info - > set_text (
String : : utf8 ( " • " ) + TTR ( " Supports desktop + mobile platforms. " ) +
String : : utf8 ( " \n • " ) + TTR ( " Less advanced 3D graphics. " ) +
String : : utf8 ( " \n • " ) + TTR ( " Less scalable for complex scenes. " ) +
String : : utf8 ( " \n • " ) + TTR ( " Uses RenderingDevice backend. " ) +
String : : utf8 ( " \n • " ) + TTR ( " Fast rendering of simple scenes. " ) ) ;
} else if ( renderer_type = = " gl_compatibility " ) {
renderer_info - > set_text (
String : : utf8 ( " • " ) + TTR ( " Supports desktop, mobile + web platforms. " ) +
String : : utf8 ( " \n • " ) + TTR ( " Least advanced 3D graphics (currently work-in-progress). " ) +
String : : utf8 ( " \n • " ) + TTR ( " Intended for low-end/older devices. " ) +
String : : utf8 ( " \n • " ) + TTR ( " Uses OpenGL 3 backend (OpenGL 3.3/ES 3.0/WebGL2). " ) +
String : : utf8 ( " \n • " ) + TTR ( " Fastest rendering of simple scenes. " ) ) ;
} else {
WARN_PRINT ( " Unknown renderer type. Please report this as a bug on GitHub. " ) ;
2022-12-13 20:24:02 +01:00
}
2023-03-10 13:57:32 +01:00
}
2022-12-13 20:24:02 +01:00
2023-03-10 13:57:32 +01:00
void ProjectDialog : : _remove_created_folder ( ) {
if ( ! created_folder_path . is_empty ( ) ) {
Ref < DirAccess > d = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
d - > remove ( created_folder_path ) ;
2014-02-10 02:10:30 +01:00
2023-03-10 13:57:32 +01:00
create_dir - > set_disabled ( false ) ;
created_folder_path = " " ;
}
}
2017-09-07 02:04:41 +02:00
2023-03-10 13:57:32 +01:00
void ProjectDialog : : ok_pressed ( ) {
String dir = project_path - > get_text ( ) ;
if ( mode = = MODE_RENAME ) {
String dir2 = _test_path ( ) ;
if ( dir2 . is_empty ( ) ) {
_set_message ( TTR ( " Invalid project path (changed anything?). " ) , MESSAGE_ERROR ) ;
return ;
}
// Load project.godot as ConfigFile to set the new name.
ConfigFile cfg ;
String project_godot = dir2 . path_join ( " project.godot " ) ;
Error err = cfg . load ( project_godot ) ;
if ( err ! = OK ) {
_set_message ( vformat ( TTR ( " Couldn't load project at '%s' (error %d). It may be missing or corrupted. " ) , project_godot , err ) , MESSAGE_ERROR ) ;
} else {
cfg . set_value ( " application " , " config/name " , project_name - > get_text ( ) . strip_edges ( ) ) ;
err = cfg . save ( project_godot ) ;
2018-02-19 14:53:59 +01:00
if ( err ! = OK ) {
2023-03-10 13:57:32 +01:00
_set_message ( vformat ( TTR ( " Couldn't save project at '%s' (error %d). " ) , project_godot , err ) , MESSAGE_ERROR ) ;
2017-09-07 02:04:41 +02:00
}
2023-03-10 13:57:32 +01:00
}
2017-09-07 02:04:41 +02:00
2023-03-10 13:57:32 +01:00
hide ( ) ;
emit_signal ( SNAME ( " projects_updated " ) ) ;
2018-12-21 12:20:48 +01:00
2023-03-10 13:57:32 +01:00
} else {
if ( mode = = MODE_IMPORT ) {
if ( project_path - > get_text ( ) . ends_with ( " .zip " ) ) {
mode = MODE_INSTALL ;
ok_pressed ( ) ;
2018-06-21 06:17:52 +02:00
2023-03-10 13:57:32 +01:00
return ;
}
} else {
if ( mode = = MODE_NEW ) {
// Before we create a project, check that the target folder is empty.
// If not, we need to ask the user if they're sure they want to do this.
if ( ! is_folder_empty ) {
ConfirmationDialog * cd = memnew ( ConfirmationDialog ) ;
cd - > set_title ( TTR ( " Warning: This folder is not empty " ) ) ;
cd - > set_text ( TTR ( " You are about to create a Godot project in a non-empty folder. \n The entire contents of this folder will be imported as project resources! \n \n Are you sure you wish to continue? " ) ) ;
cd - > get_ok_button ( ) - > connect ( " pressed " , callable_mp ( this , & ProjectDialog : : _nonempty_confirmation_ok_pressed ) ) ;
get_parent ( ) - > add_child ( cd ) ;
cd - > popup_centered ( ) ;
cd - > grab_focus ( ) ;
2018-06-21 06:17:52 +02:00
return ;
}
2023-03-10 13:57:32 +01:00
PackedStringArray project_features = ProjectSettings : : get_required_features ( ) ;
ProjectSettings : : CustomMap initial_settings ;
// Be sure to change this code if/when renderers are changed.
// Default values are "forward_plus" for the main setting, "mobile" for the mobile override,
// and "gl_compatibility" for the web override.
String renderer_type = renderer_button_group - > get_pressed_button ( ) - > get_meta ( SNAME ( " rendering_method " ) ) ;
initial_settings [ " rendering/renderer/rendering_method " ] = renderer_type ;
EditorSettings : : get_singleton ( ) - > set ( " project_manager/default_renderer " , renderer_type ) ;
EditorSettings : : get_singleton ( ) - > save ( ) ;
if ( renderer_type = = " forward_plus " ) {
project_features . push_back ( " Forward Plus " ) ;
} else if ( renderer_type = = " mobile " ) {
project_features . push_back ( " Mobile " ) ;
} else if ( renderer_type = = " gl_compatibility " ) {
project_features . push_back ( " GL Compatibility " ) ;
// Also change the default rendering method for the mobile override.
initial_settings [ " rendering/renderer/rendering_method.mobile " ] = " gl_compatibility " ;
} else {
WARN_PRINT ( " Unknown renderer type. Please report this as a bug on GitHub. " ) ;
}
2018-06-21 06:17:52 +02:00
2023-03-10 13:57:32 +01:00
project_features . sort ( ) ;
initial_settings [ " application/config/features " ] = project_features ;
initial_settings [ " application/config/name " ] = project_name - > get_text ( ) . strip_edges ( ) ;
initial_settings [ " application/config/icon " ] = " res://icon.svg " ;
2022-08-19 22:48:45 +02:00
2023-03-10 13:57:32 +01:00
if ( ProjectSettings : : get_singleton ( ) - > save_custom ( dir . path_join ( " project.godot " ) , initial_settings , Vector < String > ( ) , false ) ! = OK ) {
_set_message ( TTR ( " Couldn't create project.godot in project path. " ) , MESSAGE_ERROR ) ;
} else {
// Store default project icon in SVG format.
Error err ;
Ref < FileAccess > fa_icon = FileAccess : : open ( dir . path_join ( " icon.svg " ) , FileAccess : : WRITE , & err ) ;
fa_icon - > store_string ( get_default_project_icon ( ) ) ;
2022-08-19 22:48:45 +02:00
2023-03-10 13:57:32 +01:00
if ( err ! = OK ) {
_set_message ( TTR ( " Couldn't create icon.svg in project path. " ) , MESSAGE_ERROR ) ;
2018-06-21 06:17:52 +02:00
}
2023-03-10 13:57:32 +01:00
EditorVCSInterface : : create_vcs_metadata_files ( EditorVCSInterface : : VCSMetadata ( vcs_metadata_selection - > get_selected ( ) ) , dir ) ;
}
} else if ( mode = = MODE_INSTALL ) {
if ( project_path - > get_text ( ) . ends_with ( " .zip " ) ) {
dir = install_path - > get_text ( ) ;
zip_path = project_path - > get_text ( ) ;
}
2016-07-12 02:34:02 +02:00
2023-03-10 13:57:32 +01:00
Ref < FileAccess > io_fa ;
zlib_filefunc_def io = zipio_create_io ( & io_fa ) ;
2016-07-12 02:34:02 +02:00
2023-03-10 13:57:32 +01:00
unzFile pkg = unzOpen2 ( zip_path . utf8 ( ) . get_data ( ) , & io ) ;
if ( ! pkg ) {
dialog_error - > set_text ( TTR ( " Error opening package file, not in ZIP format. " ) ) ;
dialog_error - > popup_centered ( ) ;
return ;
}
2021-05-19 23:34:33 +02:00
2023-03-10 13:57:32 +01:00
// Find the zip_root
String zip_root ;
int ret = unzGoToFirstFile ( pkg ) ;
while ( ret = = UNZ_OK ) {
unz_file_info info ;
char fname [ 16384 ] ;
unzGetCurrentFileInfo ( pkg , & info , fname , 16384 , nullptr , 0 , nullptr , 0 ) ;
String name = String : : utf8 ( fname ) ;
if ( name . ends_with ( " project.godot " ) ) {
zip_root = name . substr ( 0 , name . rfind ( " project.godot " ) ) ;
break ;
2021-05-19 23:34:33 +02:00
}
2023-03-10 13:57:32 +01:00
ret = unzGoToNextFile ( pkg ) ;
}
2016-07-12 02:34:02 +02:00
2023-03-10 13:57:32 +01:00
ret = unzGoToFirstFile ( pkg ) ;
2016-07-12 02:34:02 +02:00
2023-03-10 13:57:32 +01:00
Vector < String > failed_files ;
while ( ret = = UNZ_OK ) {
//get filename
unz_file_info info ;
char fname [ 16384 ] ;
ret = unzGetCurrentFileInfo ( pkg , & info , fname , 16384 , nullptr , 0 , nullptr , 0 ) ;
if ( ret ! = UNZ_OK ) {
break ;
}
2016-07-12 02:34:02 +02:00
2023-03-10 13:57:32 +01:00
String path = String : : utf8 ( fname ) ;
2016-07-12 02:34:02 +02:00
2023-03-10 13:57:32 +01:00
if ( path . is_empty ( ) | | path = = zip_root | | ! zip_root . is_subsequence_of ( path ) ) {
//
} else if ( path . ends_with ( " / " ) ) { // a dir
path = path . substr ( 0 , path . length ( ) - 1 ) ;
String rel_path = path . substr ( zip_root . length ( ) ) ;
2016-07-12 02:34:02 +02:00
2023-03-10 13:57:32 +01:00
Ref < DirAccess > da = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
da - > make_dir ( dir . path_join ( rel_path ) ) ;
} else {
Vector < uint8_t > uncomp_data ;
uncomp_data . resize ( info . uncompressed_size ) ;
String rel_path = path . substr ( zip_root . length ( ) ) ;
//read
unzOpenCurrentFile ( pkg ) ;
ret = unzReadCurrentFile ( pkg , uncomp_data . ptrw ( ) , uncomp_data . size ( ) ) ;
ERR_BREAK_MSG ( ret < 0 , vformat ( " An error occurred while attempting to read from file: %s. This file will not be used. " , rel_path ) ) ;
unzCloseCurrentFile ( pkg ) ;
Ref < FileAccess > f = FileAccess : : open ( dir . path_join ( rel_path ) , FileAccess : : WRITE ) ;
if ( f . is_valid ( ) ) {
f - > store_buffer ( uncomp_data . ptr ( ) , uncomp_data . size ( ) ) ;
2017-09-07 02:04:41 +02:00
} else {
2023-03-10 13:57:32 +01:00
failed_files . push_back ( rel_path ) ;
2016-07-12 02:34:02 +02:00
}
2017-09-07 02:04:41 +02:00
}
2016-07-12 02:34:02 +02:00
2023-03-10 13:57:32 +01:00
ret = unzGoToNextFile ( pkg ) ;
}
unzClose ( pkg ) ;
2016-07-12 02:34:02 +02:00
2023-03-10 13:57:32 +01:00
if ( failed_files . size ( ) ) {
String err_msg = TTR ( " The following files failed extraction from package: " ) + " \n \n " ;
for ( int i = 0 ; i < failed_files . size ( ) ; i + + ) {
if ( i > 15 ) {
err_msg + = " \n And " + itos ( failed_files . size ( ) - i ) + " more files. " ;
break ;
2016-07-12 02:34:02 +02:00
}
2023-03-10 13:57:32 +01:00
err_msg + = failed_files [ i ] + " \n " ;
}
2016-07-12 02:34:02 +02:00
2023-03-10 13:57:32 +01:00
dialog_error - > set_text ( err_msg ) ;
dialog_error - > popup_centered ( ) ;
2016-07-12 02:34:02 +02:00
2023-03-10 13:57:32 +01:00
} else if ( ! project_path - > get_text ( ) . ends_with ( " .zip " ) ) {
dialog_error - > set_text ( TTR ( " Package installed successfully! " ) ) ;
dialog_error - > popup_centered ( ) ;
2016-07-12 02:34:02 +02:00
}
2014-02-10 02:10:30 +01:00
}
2017-08-31 03:08:17 +02:00
}
2017-12-27 00:39:46 +01:00
2023-03-10 13:57:32 +01:00
dir = dir . replace ( " \\ " , " / " ) ;
if ( dir . ends_with ( " / " ) ) {
dir = dir . substr ( 0 , dir . length ( ) - 1 ) ;
2020-05-14 16:41:43 +02:00
}
2018-06-21 06:17:52 +02:00
2023-03-10 13:57:32 +01:00
hide ( ) ;
emit_signal ( SNAME ( " project_created " ) , dir ) ;
2017-08-31 03:08:17 +02:00
}
2023-03-10 13:57:32 +01:00
}
2017-08-31 03:08:17 +02:00
2023-03-10 13:57:32 +01:00
void ProjectDialog : : cancel_pressed ( ) {
_remove_created_folder ( ) ;
2014-02-10 02:10:30 +01:00
2023-03-10 13:57:32 +01:00
project_path - > clear ( ) ;
2023-08-16 14:00:01 +02:00
_update_path ( " " ) ;
2023-03-10 13:57:32 +01:00
project_name - > clear ( ) ;
_text_changed ( " " ) ;
2014-02-10 02:10:30 +01:00
2023-08-13 02:33:39 +02:00
if ( status_rect - > get_texture ( ) = = get_editor_theme_icon ( SNAME ( " StatusError " ) ) ) {
2023-03-10 13:57:32 +01:00
msg - > show ( ) ;
2014-02-10 02:10:30 +01:00
}
2023-08-13 02:33:39 +02:00
if ( install_status_rect - > get_texture ( ) = = get_editor_theme_icon ( SNAME ( " StatusError " ) ) ) {
2023-03-10 13:57:32 +01:00
msg - > show ( ) ;
2017-09-07 02:04:41 +02:00
}
2023-03-10 13:57:32 +01:00
}
2014-02-10 02:10:30 +01:00
2023-03-10 13:57:32 +01:00
void ProjectDialog : : set_zip_path ( const String & p_path ) {
zip_path = p_path ;
}
2014-02-10 02:10:30 +01:00
2023-03-10 13:57:32 +01:00
void ProjectDialog : : set_zip_title ( const String & p_title ) {
zip_title = p_title ;
}
2014-02-10 02:10:30 +01:00
2023-03-10 13:57:32 +01:00
void ProjectDialog : : set_mode ( Mode p_mode ) {
mode = p_mode ;
}
2017-12-06 19:54:22 +01:00
2023-03-10 13:57:32 +01:00
void ProjectDialog : : set_project_path ( const String & p_path ) {
project_path - > set_text ( p_path ) ;
}
2014-02-10 02:10:30 +01:00
2023-07-30 12:09:54 +02:00
void ProjectDialog : : ask_for_path_and_show ( ) {
// Workaround: for the file selection dialog content to be rendered we need to show its parent dialog.
show_dialog ( ) ;
_set_message ( " " ) ;
_browse_path ( ) ;
}
2023-03-10 13:57:32 +01:00
void ProjectDialog : : show_dialog ( ) {
if ( mode = = MODE_RENAME ) {
project_path - > set_editable ( false ) ;
browse - > hide ( ) ;
install_browse - > hide ( ) ;
set_title ( TTR ( " Rename Project " ) ) ;
set_ok_button_text ( TTR ( " Rename " ) ) ;
name_container - > show ( ) ;
status_rect - > hide ( ) ;
msg - > hide ( ) ;
install_path_container - > hide ( ) ;
install_status_rect - > hide ( ) ;
renderer_container - > hide ( ) ;
default_files_container - > hide ( ) ;
get_ok_button ( ) - > set_disabled ( false ) ;
2017-08-31 03:08:17 +02:00
2023-03-10 13:57:32 +01:00
// Fetch current name from project.godot to prefill the text input.
ConfigFile cfg ;
String project_godot = project_path - > get_text ( ) . path_join ( " project.godot " ) ;
Error err = cfg . load ( project_godot ) ;
if ( err ! = OK ) {
_set_message ( vformat ( TTR ( " Couldn't load project at '%s' (error %d). It may be missing or corrupted. " ) , project_godot , err ) , MESSAGE_ERROR ) ;
2018-01-17 08:45:09 +01:00
status_rect - > show ( ) ;
msg - > show ( ) ;
2023-03-10 13:57:32 +01:00
get_ok_button ( ) - > set_disabled ( true ) ;
} else {
String cur_name = cfg . get_value ( " application " , " config/name " , " " ) ;
project_name - > set_text ( cur_name ) ;
_text_changed ( cur_name ) ;
2017-09-07 02:04:41 +02:00
}
2017-08-31 03:08:17 +02:00
2023-12-18 15:46:56 +01:00
callable_mp ( ( Control * ) project_name , & Control : : grab_focus ) . call_deferred ( ) ;
2014-02-10 02:10:30 +01:00
2023-03-10 13:57:32 +01:00
create_dir - > hide ( ) ;
2017-08-31 03:08:17 +02:00
2023-03-10 13:57:32 +01:00
} else {
fav_dir = EDITOR_GET ( " filesystem/directories/default_project_path " ) ;
if ( ! fav_dir . is_empty ( ) ) {
project_path - > set_text ( fav_dir ) ;
fdialog - > set_current_dir ( fav_dir ) ;
} else {
Ref < DirAccess > d = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
project_path - > set_text ( d - > get_current_dir ( ) ) ;
fdialog - > set_current_dir ( d - > get_current_dir ( ) ) ;
}
2023-11-21 12:50:01 +01:00
if ( project_name - > get_text ( ) . is_empty ( ) ) {
String proj = TTR ( " New Game Project " ) ;
project_name - > set_text ( proj ) ;
_text_changed ( proj ) ;
}
2023-03-10 13:57:32 +01:00
project_path - > set_editable ( true ) ;
browse - > set_disabled ( false ) ;
browse - > show ( ) ;
install_browse - > set_disabled ( false ) ;
install_browse - > show ( ) ;
create_dir - > show ( ) ;
status_rect - > show ( ) ;
install_status_rect - > show ( ) ;
msg - > show ( ) ;
2014-02-10 02:10:30 +01:00
2023-03-10 13:57:32 +01:00
if ( mode = = MODE_IMPORT ) {
set_title ( TTR ( " Import Existing Project " ) ) ;
set_ok_button_text ( TTR ( " Import & Edit " ) ) ;
name_container - > hide ( ) ;
install_path_container - > hide ( ) ;
renderer_container - > hide ( ) ;
default_files_container - > hide ( ) ;
project_path - > grab_focus ( ) ;
2014-02-10 02:10:30 +01:00
2023-03-10 13:57:32 +01:00
} else if ( mode = = MODE_NEW ) {
set_title ( TTR ( " Create New Project " ) ) ;
set_ok_button_text ( TTR ( " Create & Edit " ) ) ;
name_container - > show ( ) ;
install_path_container - > hide ( ) ;
renderer_container - > show ( ) ;
default_files_container - > show ( ) ;
2023-12-18 15:46:56 +01:00
callable_mp ( ( Control * ) project_name , & Control : : grab_focus ) . call_deferred ( ) ;
callable_mp ( project_name , & LineEdit : : select_all ) . call_deferred ( ) ;
2018-06-21 06:17:52 +02:00
2023-03-10 13:57:32 +01:00
} else if ( mode = = MODE_INSTALL ) {
set_title ( TTR ( " Install Project: " ) + " " + zip_title ) ;
set_ok_button_text ( TTR ( " Install & Edit " ) ) ;
project_name - > set_text ( zip_title ) ;
name_container - > show ( ) ;
install_path_container - > hide ( ) ;
renderer_container - > hide ( ) ;
default_files_container - > hide ( ) ;
project_path - > grab_focus ( ) ;
}
2018-06-21 06:17:52 +02:00
2023-03-10 13:57:32 +01:00
_test_path ( ) ;
}
2014-02-10 02:10:30 +01:00
2023-03-10 13:57:32 +01:00
popup_centered ( Size2 ( 500 , 0 ) * EDSCALE ) ;
}
2019-01-22 00:59:53 +01:00
2023-03-10 13:57:32 +01:00
void ProjectDialog : : _notification ( int p_what ) {
switch ( p_what ) {
case NOTIFICATION_WM_CLOSE_REQUEST : {
_remove_created_folder ( ) ;
} break ;
}
}
2022-12-13 20:24:02 +01:00
2023-03-10 13:57:32 +01:00
void ProjectDialog : : _bind_methods ( ) {
ADD_SIGNAL ( MethodInfo ( " project_created " ) ) ;
ADD_SIGNAL ( MethodInfo ( " projects_updated " ) ) ;
}
2022-12-13 20:24:02 +01:00
2023-03-10 13:57:32 +01:00
ProjectDialog : : ProjectDialog ( ) {
VBoxContainer * vb = memnew ( VBoxContainer ) ;
add_child ( vb ) ;
name_container = memnew ( VBoxContainer ) ;
vb - > add_child ( name_container ) ;
Label * l = memnew ( Label ) ;
l - > set_text ( TTR ( " Project Name: " ) ) ;
name_container - > add_child ( l ) ;
HBoxContainer * pnhb = memnew ( HBoxContainer ) ;
name_container - > add_child ( pnhb ) ;
project_name = memnew ( LineEdit ) ;
project_name - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
pnhb - > add_child ( project_name ) ;
create_dir = memnew ( Button ) ;
pnhb - > add_child ( create_dir ) ;
create_dir - > set_text ( TTR ( " Create Folder " ) ) ;
create_dir - > connect ( " pressed " , callable_mp ( this , & ProjectDialog : : _create_folder ) ) ;
path_container = memnew ( VBoxContainer ) ;
vb - > add_child ( path_container ) ;
l = memnew ( Label ) ;
l - > set_text ( TTR ( " Project Path: " ) ) ;
path_container - > add_child ( l ) ;
HBoxContainer * pphb = memnew ( HBoxContainer ) ;
path_container - > add_child ( pphb ) ;
project_path = memnew ( LineEdit ) ;
project_path - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
project_path - > set_structured_text_bidi_override ( TextServer : : STRUCTURED_TEXT_FILE ) ;
pphb - > add_child ( project_path ) ;
install_path_container = memnew ( VBoxContainer ) ;
vb - > add_child ( install_path_container ) ;
l = memnew ( Label ) ;
l - > set_text ( TTR ( " Project Installation Path: " ) ) ;
install_path_container - > add_child ( l ) ;
HBoxContainer * iphb = memnew ( HBoxContainer ) ;
install_path_container - > add_child ( iphb ) ;
install_path = memnew ( LineEdit ) ;
install_path - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
install_path - > set_structured_text_bidi_override ( TextServer : : STRUCTURED_TEXT_FILE ) ;
iphb - > add_child ( install_path ) ;
// status icon
status_rect = memnew ( TextureRect ) ;
status_rect - > set_stretch_mode ( TextureRect : : STRETCH_KEEP_CENTERED ) ;
pphb - > add_child ( status_rect ) ;
browse = memnew ( Button ) ;
browse - > set_text ( TTR ( " Browse " ) ) ;
browse - > connect ( " pressed " , callable_mp ( this , & ProjectDialog : : _browse_path ) ) ;
pphb - > add_child ( browse ) ;
// install status icon
install_status_rect = memnew ( TextureRect ) ;
install_status_rect - > set_stretch_mode ( TextureRect : : STRETCH_KEEP_CENTERED ) ;
iphb - > add_child ( install_status_rect ) ;
install_browse = memnew ( Button ) ;
install_browse - > set_text ( TTR ( " Browse " ) ) ;
install_browse - > connect ( " pressed " , callable_mp ( this , & ProjectDialog : : _browse_install_path ) ) ;
iphb - > add_child ( install_browse ) ;
msg = memnew ( Label ) ;
msg - > set_horizontal_alignment ( HORIZONTAL_ALIGNMENT_CENTER ) ;
2023-11-24 16:31:24 +01:00
msg - > set_custom_minimum_size ( Size2 ( 200 , 0 ) * EDSCALE ) ;
2023-03-10 13:57:32 +01:00
vb - > add_child ( msg ) ;
// Renderer selection.
renderer_container = memnew ( VBoxContainer ) ;
vb - > add_child ( renderer_container ) ;
l = memnew ( Label ) ;
l - > set_text ( TTR ( " Renderer: " ) ) ;
renderer_container - > add_child ( l ) ;
HBoxContainer * rshc = memnew ( HBoxContainer ) ;
renderer_container - > add_child ( rshc ) ;
renderer_button_group . instantiate ( ) ;
// Left hand side, used for checkboxes to select renderer.
Container * rvb = memnew ( VBoxContainer ) ;
rshc - > add_child ( rvb ) ;
String default_renderer_type = " forward_plus " ;
if ( EditorSettings : : get_singleton ( ) - > has_setting ( " project_manager/default_renderer " ) ) {
default_renderer_type = EditorSettings : : get_singleton ( ) - > get_setting ( " project_manager/default_renderer " ) ;
}
Button * rs_button = memnew ( CheckBox ) ;
rs_button - > set_button_group ( renderer_button_group ) ;
rs_button - > set_text ( TTR ( " Forward+ " ) ) ;
2023-01-31 17:23:59 +01:00
# if defined(WEB_ENABLED)
2023-03-10 13:57:32 +01:00
rs_button - > set_disabled ( true ) ;
2023-01-31 17:23:59 +01:00
# endif
2023-03-10 13:57:32 +01:00
rs_button - > set_meta ( SNAME ( " rendering_method " ) , " forward_plus " ) ;
rs_button - > connect ( " pressed " , callable_mp ( this , & ProjectDialog : : _renderer_selected ) ) ;
rvb - > add_child ( rs_button ) ;
if ( default_renderer_type = = " forward_plus " ) {
rs_button - > set_pressed ( true ) ;
}
rs_button = memnew ( CheckBox ) ;
rs_button - > set_button_group ( renderer_button_group ) ;
rs_button - > set_text ( TTR ( " Mobile " ) ) ;
2023-01-31 17:23:59 +01:00
# if defined(WEB_ENABLED)
2023-03-10 13:57:32 +01:00
rs_button - > set_disabled ( true ) ;
2023-01-31 17:23:59 +01:00
# endif
2023-03-10 13:57:32 +01:00
rs_button - > set_meta ( SNAME ( " rendering_method " ) , " mobile " ) ;
rs_button - > connect ( " pressed " , callable_mp ( this , & ProjectDialog : : _renderer_selected ) ) ;
rvb - > add_child ( rs_button ) ;
if ( default_renderer_type = = " mobile " ) {
rs_button - > set_pressed ( true ) ;
}
rs_button = memnew ( CheckBox ) ;
rs_button - > set_button_group ( renderer_button_group ) ;
rs_button - > set_text ( TTR ( " Compatibility " ) ) ;
2023-01-31 17:23:59 +01:00
# if !defined(GLES3_ENABLED)
2023-03-10 13:57:32 +01:00
rs_button - > set_disabled ( true ) ;
2023-01-31 17:23:59 +01:00
# endif
2023-03-10 13:57:32 +01:00
rs_button - > set_meta ( SNAME ( " rendering_method " ) , " gl_compatibility " ) ;
rs_button - > connect ( " pressed " , callable_mp ( this , & ProjectDialog : : _renderer_selected ) ) ;
rvb - > add_child ( rs_button ) ;
2023-01-31 17:23:59 +01:00
# if defined(GLES3_ENABLED)
2023-03-10 13:57:32 +01:00
if ( default_renderer_type = = " gl_compatibility " ) {
rs_button - > set_pressed ( true ) ;
2019-07-19 23:37:45 +02:00
}
2023-03-10 13:57:32 +01:00
# endif
rshc - > add_child ( memnew ( VSeparator ) ) ;
// Right hand side, used for text explaining each choice.
rvb = memnew ( VBoxContainer ) ;
rvb - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
rshc - > add_child ( rvb ) ;
renderer_info = memnew ( Label ) ;
renderer_info - > set_modulate ( Color ( 1 , 1 , 1 , 0.7 ) ) ;
rvb - > add_child ( renderer_info ) ;
_renderer_selected ( ) ;
l = memnew ( Label ) ;
l - > set_text ( TTR ( " The renderer can be changed later, but scenes may need to be adjusted. " ) ) ;
// Add some extra spacing to separate it from the list above and the buttons below.
l - > set_custom_minimum_size ( Size2 ( 0 , 40 ) * EDSCALE ) ;
l - > set_horizontal_alignment ( HORIZONTAL_ALIGNMENT_CENTER ) ;
l - > set_vertical_alignment ( VERTICAL_ALIGNMENT_CENTER ) ;
l - > set_modulate ( Color ( 1 , 1 , 1 , 0.7 ) ) ;
renderer_container - > add_child ( l ) ;
default_files_container = memnew ( HBoxContainer ) ;
vb - > add_child ( default_files_container ) ;
l = memnew ( Label ) ;
l - > set_text ( TTR ( " Version Control Metadata: " ) ) ;
default_files_container - > add_child ( l ) ;
vcs_metadata_selection = memnew ( OptionButton ) ;
vcs_metadata_selection - > set_custom_minimum_size ( Size2 ( 100 , 20 ) ) ;
vcs_metadata_selection - > add_item ( TTR ( " None " ) , ( int ) EditorVCSInterface : : VCSMetadata : : NONE ) ;
vcs_metadata_selection - > add_item ( TTR ( " Git " ) , ( int ) EditorVCSInterface : : VCSMetadata : : GIT ) ;
vcs_metadata_selection - > select ( ( int ) EditorVCSInterface : : VCSMetadata : : GIT ) ;
default_files_container - > add_child ( vcs_metadata_selection ) ;
Control * spacer = memnew ( Control ) ;
spacer - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
default_files_container - > add_child ( spacer ) ;
fdialog = memnew ( EditorFileDialog ) ;
fdialog - > set_previews_enabled ( false ) ; //Crucial, otherwise the engine crashes.
fdialog - > set_access ( EditorFileDialog : : ACCESS_FILESYSTEM ) ;
fdialog_install = memnew ( EditorFileDialog ) ;
fdialog_install - > set_previews_enabled ( false ) ; //Crucial, otherwise the engine crashes.
fdialog_install - > set_access ( EditorFileDialog : : ACCESS_FILESYSTEM ) ;
add_child ( fdialog ) ;
add_child ( fdialog_install ) ;
project_name - > connect ( " text_changed " , callable_mp ( this , & ProjectDialog : : _text_changed ) ) ;
project_path - > connect ( " text_changed " , callable_mp ( this , & ProjectDialog : : _path_text_changed ) ) ;
2023-08-16 14:00:01 +02:00
install_path - > connect ( " text_changed " , callable_mp ( this , & ProjectDialog : : _update_path ) ) ;
2023-03-10 13:57:32 +01:00
fdialog - > connect ( " dir_selected " , callable_mp ( this , & ProjectDialog : : _path_selected ) ) ;
fdialog - > connect ( " file_selected " , callable_mp ( this , & ProjectDialog : : _file_selected ) ) ;
fdialog_install - > connect ( " dir_selected " , callable_mp ( this , & ProjectDialog : : _install_path_selected ) ) ;
fdialog_install - > connect ( " file_selected " , callable_mp ( this , & ProjectDialog : : _install_path_selected ) ) ;
set_hide_on_ok ( false ) ;
dialog_error = memnew ( AcceptDialog ) ;
add_child ( dialog_error ) ;
}
2019-07-19 23:37:45 +02:00
2023-03-10 13:57:32 +01:00
/// Project List and friends.
2019-09-13 02:31:02 +02:00
2023-03-10 13:57:32 +01:00
void ProjectListItemControl : : _notification ( int p_what ) {
switch ( p_what ) {
2023-03-10 17:01:08 +01:00
case NOTIFICATION_THEME_CHANGED : {
if ( icon_needs_reload ) {
// The project icon may not be loaded by the time the control is displayed,
// so use a loading placeholder.
2023-08-13 02:33:39 +02:00
project_icon - > set_texture ( get_editor_theme_icon ( SNAME ( " ProjectIconLoading " ) ) ) ;
2023-03-10 17:01:08 +01:00
}
2023-10-19 18:05:19 +02:00
project_title - > begin_bulk_theme_override ( ) ;
2023-08-13 02:33:39 +02:00
project_title - > add_theme_font_override ( " font " , get_theme_font ( SNAME ( " title " ) , EditorStringName ( EditorFonts ) ) ) ;
project_title - > add_theme_font_size_override ( " font_size " , get_theme_font_size ( SNAME ( " title_size " ) , EditorStringName ( EditorFonts ) ) ) ;
2023-03-10 17:01:08 +01:00
project_title - > add_theme_color_override ( " font_color " , get_theme_color ( SNAME ( " font_color " ) , SNAME ( " Tree " ) ) ) ;
2023-10-19 18:05:19 +02:00
project_title - > end_bulk_theme_override ( ) ;
2023-03-10 17:01:08 +01:00
project_path - > add_theme_color_override ( " font_color " , get_theme_color ( SNAME ( " font_color " ) , SNAME ( " Tree " ) ) ) ;
2023-08-13 02:33:39 +02:00
project_unsupported_features - > set_texture ( get_editor_theme_icon ( SNAME ( " NodeWarning " ) ) ) ;
2023-03-10 17:01:08 +01:00
2023-08-13 02:33:39 +02:00
favorite_button - > set_texture_normal ( get_editor_theme_icon ( SNAME ( " Favorites " ) ) ) ;
2023-03-10 17:01:08 +01:00
if ( project_is_missing ) {
2023-08-13 02:33:39 +02:00
explore_button - > set_icon ( get_editor_theme_icon ( SNAME ( " FileBroken " ) ) ) ;
2023-03-10 17:01:08 +01:00
} else {
2023-08-13 02:33:39 +02:00
explore_button - > set_icon ( get_editor_theme_icon ( SNAME ( " Load " ) ) ) ;
2023-03-10 17:01:08 +01:00
}
} break ;
2023-03-10 13:57:32 +01:00
case NOTIFICATION_MOUSE_ENTER : {
2023-03-10 17:01:08 +01:00
is_hovering = true ;
2023-03-10 13:57:32 +01:00
queue_redraw ( ) ;
} break ;
2022-02-16 15:17:55 +01:00
2023-03-10 13:57:32 +01:00
case NOTIFICATION_MOUSE_EXIT : {
2023-03-10 17:01:08 +01:00
is_hovering = false ;
2023-03-10 13:57:32 +01:00
queue_redraw ( ) ;
} break ;
2022-02-16 15:17:55 +01:00
2023-03-10 13:57:32 +01:00
case NOTIFICATION_DRAW : {
2023-03-10 17:01:08 +01:00
if ( is_selected ) {
draw_style_box ( get_theme_stylebox ( SNAME ( " selected " ) , SNAME ( " Tree " ) ) , Rect2 ( Point2 ( ) , get_size ( ) ) ) ;
}
if ( is_hovering ) {
2023-03-10 13:57:32 +01:00
draw_style_box ( get_theme_stylebox ( SNAME ( " hover " ) , SNAME ( " Tree " ) ) , Rect2 ( Point2 ( ) , get_size ( ) ) ) ;
}
2023-03-10 17:01:08 +01:00
draw_line ( Point2 ( 0 , get_size ( ) . y + 1 ) , Point2 ( get_size ( ) . x , get_size ( ) . y + 1 ) , get_theme_color ( SNAME ( " guide_color " ) , SNAME ( " Tree " ) ) ) ;
2023-03-10 13:57:32 +01:00
} break ;
2019-09-13 02:31:02 +02:00
}
2023-03-10 13:57:32 +01:00
}
2019-07-19 23:37:45 +02:00
2023-03-10 17:01:08 +01:00
void ProjectListItemControl : : set_project_title ( const String & p_title ) {
project_title - > set_text ( p_title ) ;
}
void ProjectListItemControl : : set_project_path ( const String & p_path ) {
project_path - > set_text ( p_path ) ;
}
2023-03-17 23:30:21 +01:00
void ProjectListItemControl : : set_tags ( const PackedStringArray & p_tags , ProjectList * p_parent_list ) {
for ( const String & tag : p_tags ) {
ProjectTag * tag_control = memnew ( ProjectTag ( tag ) ) ;
tag_container - > add_child ( tag_control ) ;
tag_control - > connect_button_to ( callable_mp ( p_parent_list , & ProjectList : : add_search_tag ) . bind ( tag ) ) ;
}
}
2023-03-10 17:01:08 +01:00
void ProjectListItemControl : : set_project_icon ( const Ref < Texture2D > & p_icon ) {
icon_needs_reload = false ;
// The default project icon is 128× 128 to look crisp on hiDPI displays,
// but we want the actual displayed size to be 64× 64 on loDPI displays.
project_icon - > set_expand_mode ( TextureRect : : EXPAND_IGNORE_SIZE ) ;
project_icon - > set_custom_minimum_size ( Size2 ( 64 , 64 ) * EDSCALE ) ;
project_icon - > set_stretch_mode ( TextureRect : : STRETCH_KEEP_ASPECT_CENTERED ) ;
project_icon - > set_texture ( p_icon ) ;
}
2023-07-07 00:37:24 +02:00
bool _project_feature_looks_like_version ( const String & p_feature ) {
return p_feature . contains ( " . " ) & & p_feature . substr ( 0 , 3 ) . is_numeric ( ) ;
}
void ProjectListItemControl : : set_unsupported_features ( PackedStringArray p_features ) {
2023-03-10 17:01:08 +01:00
if ( p_features . size ( ) > 0 ) {
2023-07-07 00:37:24 +02:00
String tooltip_text = " " ;
for ( int i = 0 ; i < p_features . size ( ) ; i + + ) {
if ( _project_feature_looks_like_version ( p_features [ i ] ) ) {
tooltip_text + = TTR ( " This project was last edited in a different Godot version: " ) + p_features [ i ] + " \n " ;
p_features . remove_at ( i ) ;
i - - ;
}
}
if ( p_features . size ( ) > 0 ) {
String unsupported_features_str = String ( " , " ) . join ( p_features ) ;
tooltip_text + = TTR ( " This project uses features unsupported by the current build: " ) + " \n " + unsupported_features_str ;
}
project_unsupported_features - > set_tooltip_text ( tooltip_text ) ;
2023-03-10 17:01:08 +01:00
project_unsupported_features - > show ( ) ;
} else {
project_unsupported_features - > hide ( ) ;
}
}
bool ProjectListItemControl : : should_load_project_icon ( ) const {
return icon_needs_reload ;
}
void ProjectListItemControl : : set_selected ( bool p_selected ) {
is_selected = p_selected ;
queue_redraw ( ) ;
}
void ProjectListItemControl : : set_is_favorite ( bool p_favorite ) {
favorite_button - > set_modulate ( p_favorite ? Color ( 1 , 1 , 1 , 1 ) : Color ( 1 , 1 , 1 , 0.2 ) ) ;
}
void ProjectListItemControl : : set_is_missing ( bool p_missing ) {
if ( project_is_missing = = p_missing ) {
return ;
}
project_is_missing = p_missing ;
if ( project_is_missing ) {
project_icon - > set_modulate ( Color ( 1 , 1 , 1 , 0.5 ) ) ;
2023-08-13 02:33:39 +02:00
explore_button - > set_icon ( get_editor_theme_icon ( SNAME ( " FileBroken " ) ) ) ;
2023-03-10 17:01:08 +01:00
explore_button - > set_tooltip_text ( TTR ( " Error: Project is missing on the filesystem. " ) ) ;
} else {
project_icon - > set_modulate ( Color ( 1 , 1 , 1 , 1.0 ) ) ;
2023-08-13 02:33:39 +02:00
explore_button - > set_icon ( get_editor_theme_icon ( SNAME ( " Load " ) ) ) ;
2023-03-10 17:01:08 +01:00
# if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
explore_button - > set_tooltip_text ( TTR ( " Show in File Manager " ) ) ;
# else
// Opening the system file manager is not supported on the Android and web editors.
explore_button - > hide ( ) ;
# endif
}
}
void ProjectListItemControl : : set_is_grayed ( bool p_grayed ) {
if ( p_grayed ) {
main_vbox - > set_modulate ( Color ( 1 , 1 , 1 , 0.5 ) ) ;
// Don't make the icon less prominent if the parent is already grayed out.
explore_button - > set_modulate ( Color ( 1 , 1 , 1 , 1.0 ) ) ;
} else {
main_vbox - > set_modulate ( Color ( 1 , 1 , 1 , 1.0 ) ) ;
explore_button - > set_modulate ( Color ( 1 , 1 , 1 , 0.5 ) ) ;
}
}
void ProjectListItemControl : : _favorite_button_pressed ( ) {
emit_signal ( SNAME ( " favorite_pressed " ) ) ;
}
void ProjectListItemControl : : _explore_button_pressed ( ) {
emit_signal ( SNAME ( " explore_pressed " ) ) ;
}
void ProjectListItemControl : : _bind_methods ( ) {
ADD_SIGNAL ( MethodInfo ( " favorite_pressed " ) ) ;
ADD_SIGNAL ( MethodInfo ( " explore_pressed " ) ) ;
2023-03-10 13:57:32 +01:00
}
2019-07-19 23:37:45 +02:00
2023-03-10 13:57:32 +01:00
ProjectListItemControl : : ProjectListItemControl ( ) {
set_focus_mode ( FocusMode : : FOCUS_ALL ) ;
2023-03-10 17:01:08 +01:00
VBoxContainer * favorite_box = memnew ( VBoxContainer ) ;
favorite_box - > set_alignment ( BoxContainer : : ALIGNMENT_CENTER ) ;
add_child ( favorite_box ) ;
favorite_button = memnew ( TextureButton ) ;
favorite_button - > set_name ( " FavoriteButton " ) ;
// This makes the project's "hover" style display correctly when hovering the favorite icon.
favorite_button - > set_mouse_filter ( MOUSE_FILTER_PASS ) ;
favorite_box - > add_child ( favorite_button ) ;
favorite_button - > connect ( " pressed " , callable_mp ( this , & ProjectListItemControl : : _favorite_button_pressed ) ) ;
project_icon = memnew ( TextureRect ) ;
project_icon - > set_name ( " ProjectIcon " ) ;
project_icon - > set_v_size_flags ( SIZE_SHRINK_CENTER ) ;
add_child ( project_icon ) ;
main_vbox = memnew ( VBoxContainer ) ;
main_vbox - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
add_child ( main_vbox ) ;
Control * ec = memnew ( Control ) ;
ec - > set_custom_minimum_size ( Size2 ( 0 , 1 ) ) ;
ec - > set_mouse_filter ( MOUSE_FILTER_PASS ) ;
main_vbox - > add_child ( ec ) ;
2023-03-17 23:30:21 +01:00
// Top half, title, tags and unsupported features labels.
2023-03-10 17:01:08 +01:00
{
HBoxContainer * title_hb = memnew ( HBoxContainer ) ;
main_vbox - > add_child ( title_hb ) ;
project_title = memnew ( Label ) ;
2023-06-09 12:18:53 +02:00
project_title - > set_auto_translate ( false ) ;
2023-03-10 17:01:08 +01:00
project_title - > set_name ( " ProjectName " ) ;
project_title - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
project_title - > set_clip_text ( true ) ;
title_hb - > add_child ( project_title ) ;
2023-03-17 23:30:21 +01:00
tag_container = memnew ( HBoxContainer ) ;
title_hb - > add_child ( tag_container ) ;
2023-03-10 17:01:08 +01:00
Control * spacer = memnew ( Control ) ;
spacer - > set_custom_minimum_size ( Size2 ( 10 , 10 ) ) ;
title_hb - > add_child ( spacer ) ;
}
// Bottom half, containing the path and view folder button.
{
HBoxContainer * path_hb = memnew ( HBoxContainer ) ;
path_hb - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
main_vbox - > add_child ( path_hb ) ;
explore_button = memnew ( Button ) ;
explore_button - > set_name ( " ExploreButton " ) ;
explore_button - > set_flat ( true ) ;
path_hb - > add_child ( explore_button ) ;
explore_button - > connect ( " pressed " , callable_mp ( this , & ProjectListItemControl : : _explore_button_pressed ) ) ;
project_path = memnew ( Label ) ;
project_path - > set_name ( " ProjectPath " ) ;
project_path - > set_structured_text_bidi_override ( TextServer : : STRUCTURED_TEXT_FILE ) ;
project_path - > set_clip_text ( true ) ;
project_path - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
project_path - > set_modulate ( Color ( 1 , 1 , 1 , 0.5 ) ) ;
path_hb - > add_child ( project_path ) ;
2023-03-17 23:30:21 +01:00
project_unsupported_features = memnew ( TextureRect ) ;
project_unsupported_features - > set_name ( " ProjectUnsupportedFeatures " ) ;
project_unsupported_features - > set_stretch_mode ( TextureRect : : STRETCH_KEEP_CENTERED ) ;
path_hb - > add_child ( project_unsupported_features ) ;
project_unsupported_features - > hide ( ) ;
Control * spacer = memnew ( Control ) ;
spacer - > set_custom_minimum_size ( Size2 ( 10 , 10 ) ) ;
path_hb - > add_child ( spacer ) ;
2023-03-10 17:01:08 +01:00
}
2023-03-10 13:57:32 +01:00
}
2019-07-19 23:37:45 +02:00
struct ProjectListComparator {
2023-03-10 13:57:32 +01:00
ProjectList : : FilterOption order_option = ProjectList : : FilterOption : : EDIT_DATE ;
2019-07-19 23:37:45 +02:00
// operator<
_FORCE_INLINE_ bool operator ( ) ( const ProjectList : : Item & a , const ProjectList : : Item & b ) const {
if ( a . favorite & & ! b . favorite ) {
return true ;
}
if ( b . favorite & & ! a . favorite ) {
return false ;
}
switch ( order_option ) {
2023-03-10 13:57:32 +01:00
case ProjectList : : PATH :
2022-02-24 02:19:27 +01:00
return a . path < b . path ;
2023-03-10 13:57:32 +01:00
case ProjectList : : EDIT_DATE :
2020-02-12 18:13:32 +01:00
return a . last_edited > b . last_edited ;
2023-03-17 23:30:21 +01:00
case ProjectList : : TAGS :
return a . tag_sort_string < b . tag_sort_string ;
2019-06-18 01:37:06 +02:00
default :
2019-07-19 23:37:45 +02:00
return a . project_name < b . project_name ;
2019-06-18 01:37:06 +02:00
}
2014-05-01 13:30:10 +02:00
}
} ;
2014-02-10 02:10:30 +01:00
2023-12-20 13:09:36 +01:00
const char * ProjectList : : SIGNAL_LIST_CHANGED = " list_changed " ;
2023-03-10 13:57:32 +01:00
const char * ProjectList : : SIGNAL_SELECTION_CHANGED = " selection_changed " ;
const char * ProjectList : : SIGNAL_PROJECT_ASK_OPEN = " project_ask_open " ;
2019-06-09 22:33:50 +02:00
2019-07-19 23:37:45 +02:00
void ProjectList : : _notification ( int p_what ) {
2022-02-16 15:17:55 +01:00
switch ( p_what ) {
case NOTIFICATION_PROCESS : {
// Load icons as a coroutine to speed up launch when you have hundreds of projects
if ( _icon_load_index < _projects . size ( ) ) {
Item & item = _projects . write [ _icon_load_index ] ;
2023-03-10 17:01:08 +01:00
if ( item . control - > should_load_project_icon ( ) ) {
2023-04-04 15:27:13 +02:00
_load_project_icon ( _icon_load_index ) ;
2022-02-16 15:17:55 +01:00
}
_icon_load_index + + ;
2019-07-19 23:37:45 +02:00
2022-02-16 15:17:55 +01:00
} else {
set_process ( false ) ;
}
} break ;
2015-12-14 20:31:37 +01:00
}
}
2014-05-11 03:45:52 +02:00
2023-04-04 15:27:13 +02:00
void ProjectList : : _update_icons_async ( ) {
2023-03-10 13:57:32 +01:00
_icon_load_index = 0 ;
set_process ( true ) ;
}
2023-04-04 15:27:13 +02:00
void ProjectList : : _load_project_icon ( int p_index ) {
2019-07-19 23:37:45 +02:00
Item & item = _projects . write [ p_index ] ;
2023-08-13 02:33:39 +02:00
Ref < Texture2D > default_icon = get_editor_theme_icon ( SNAME ( " DefaultProjectIcon " ) ) ;
2019-06-11 20:43:37 +02:00
Ref < Texture2D > icon ;
2021-12-09 10:42:46 +01:00
if ( ! item . icon . is_empty ( ) ) {
2019-07-19 23:37:45 +02:00
Ref < Image > img ;
2021-06-18 00:03:09 +02:00
img . instantiate ( ) ;
2019-07-19 23:37:45 +02:00
Error err = img - > load ( item . icon . replace_first ( " res:// " , item . path + " / " ) ) ;
if ( err = = OK ) {
2019-08-13 22:08:23 +02:00
img - > resize ( default_icon - > get_width ( ) , default_icon - > get_height ( ) , Image : : INTERPOLATE_LANCZOS ) ;
2022-05-04 01:49:20 +02:00
icon = ImageTexture : : create_from_image ( img ) ;
2019-07-19 23:37:45 +02:00
}
}
if ( icon . is_null ( ) ) {
icon = default_icon ;
}
2019-06-09 22:33:50 +02:00
2023-03-10 17:01:08 +01:00
item . control - > set_project_icon ( icon ) ;
2019-06-09 22:33:50 +02:00
}
2023-03-10 13:57:32 +01:00
// Load project data from p_property_key and return it in a ProjectList::Item.
// p_favorite is passed directly into the Item.
2022-02-24 02:19:27 +01:00
ProjectList : : Item ProjectList : : load_project_data ( const String & p_path , bool p_favorite ) {
2022-08-30 02:34:01 +02:00
String conf = p_path . path_join ( " project.godot " ) ;
2019-07-19 23:37:45 +02:00
bool grayed = false ;
bool missing = false ;
2014-02-10 02:10:30 +01:00
2019-07-19 23:37:45 +02:00
Ref < ConfigFile > cf = memnew ( ConfigFile ) ;
Error cf_err = cf - > load ( conf ) ;
2014-02-10 02:10:30 +01:00
2019-07-19 23:37:45 +02:00
int config_version = 0 ;
String project_name = TTR ( " Unnamed Project " ) ;
if ( cf_err = = OK ) {
2023-03-17 23:30:21 +01:00
String cf_project_name = cf - > get_value ( " application " , " config/name " , " " ) ;
2021-12-09 10:42:46 +01:00
if ( ! cf_project_name . is_empty ( ) ) {
2019-07-19 23:37:45 +02:00
project_name = cf_project_name . xml_unescape ( ) ;
2020-05-14 16:41:43 +02:00
}
2019-07-19 23:37:45 +02:00
config_version = ( int ) cf - > get_value ( " " , " config_version " , 0 ) ;
2014-02-10 02:10:30 +01:00
}
2019-07-19 23:37:45 +02:00
if ( config_version > ProjectSettings : : CONFIG_VERSION ) {
2021-11-24 17:12:56 +01:00
// Comes from an incompatible (more recent) Godot version, gray it out.
2019-07-19 23:37:45 +02:00
grayed = true ;
}
2021-11-24 17:12:56 +01:00
const String description = cf - > get_value ( " application " , " config/description " , " " ) ;
2023-03-17 23:30:21 +01:00
const PackedStringArray tags = cf - > get_value ( " application " , " config/tags " , PackedStringArray ( ) ) ;
2021-11-24 17:12:56 +01:00
const String icon = cf - > get_value ( " application " , " config/icon " , " " ) ;
const String main_scene = cf - > get_value ( " application " , " run/main_scene " , " " ) ;
PackedStringArray project_features = cf - > get_value ( " application " , " config/features " , PackedStringArray ( ) ) ;
PackedStringArray unsupported_features = ProjectSettings : : get_unsupported_features ( project_features ) ;
2019-07-19 23:37:45 +02:00
2020-02-12 18:13:32 +01:00
uint64_t last_edited = 0 ;
2022-11-09 14:00:51 +01:00
if ( cf_err = = OK ) {
2020-02-12 18:13:32 +01:00
// The modification date marks the date the project was last edited.
// This is because the `project.godot` file will always be modified
// when editing a project (but not when running it).
last_edited = FileAccess : : get_modified_time ( conf ) ;
2019-07-19 23:37:45 +02:00
2022-08-30 02:34:01 +02:00
String fscache = p_path . path_join ( " .fscache " ) ;
2019-07-19 23:37:45 +02:00
if ( FileAccess : : exists ( fscache ) ) {
uint64_t cache_modified = FileAccess : : get_modified_time ( fscache ) ;
2020-05-14 16:41:43 +02:00
if ( cache_modified > last_edited ) {
2020-02-12 18:13:32 +01:00
last_edited = cache_modified ;
2020-05-14 16:41:43 +02:00
}
2019-07-19 23:37:45 +02:00
}
} else {
grayed = true ;
missing = true ;
print_line ( " Project is missing: " + conf ) ;
}
2023-03-17 23:30:21 +01:00
for ( const String & tag : tags ) {
ProjectManager : : get_singleton ( ) - > add_new_tag ( tag ) ;
}
return Item ( project_name , description , tags , p_path , icon , main_scene , unsupported_features , last_edited , p_favorite , grayed , missing , config_version ) ;
2022-02-24 02:19:27 +01:00
}
2023-12-20 13:09:36 +01:00
void ProjectList : : _migrate_config ( ) {
2022-02-24 02:19:27 +01:00
// Proposal #1637 moved the project list from editor settings to a separate config file
// If the new config file doesn't exist, populate it from EditorSettings
if ( FileAccess : : exists ( _config_path ) ) {
return ;
}
List < PropertyInfo > properties ;
EditorSettings : : get_singleton ( ) - > get_property_list ( & properties ) ;
2019-07-19 23:37:45 +02:00
2022-02-24 02:19:27 +01:00
for ( const PropertyInfo & E : properties ) {
// This is actually something like "projects/C:::Documents::Godot::Projects::MyGame"
String property_key = E . name ;
if ( ! property_key . begins_with ( " projects/ " ) ) {
continue ;
}
2022-10-18 16:43:37 +02:00
String path = EDITOR_GET ( property_key ) ;
2022-09-13 22:09:16 +02:00
print_line ( " Migrating legacy project ' " + path + " '. " ) ;
2022-02-24 02:19:27 +01:00
String favoriteKey = " favorite_projects/ " + property_key . get_slice ( " / " , 1 ) ;
bool favorite = EditorSettings : : get_singleton ( ) - > has_setting ( favoriteKey ) ;
add_project ( path , favorite ) ;
if ( favorite ) {
EditorSettings : : get_singleton ( ) - > erase ( favoriteKey ) ;
}
EditorSettings : : get_singleton ( ) - > erase ( property_key ) ;
}
save_config ( ) ;
2014-02-10 02:10:30 +01:00
}
2023-12-20 13:09:36 +01:00
void ProjectList : : update_project_list ( ) {
2019-07-19 23:37:45 +02:00
// This is a full, hard reload of the list. Don't call this unless really required, it's expensive.
// If you have 150 projects, it may read through 150 files on your disk at once + load 150 icons.
2023-12-20 13:09:36 +01:00
// FIXME: Does it really have to be a full, hard reload? Runtime updates should be made much cheaper.
2016-08-05 16:06:16 +02:00
2019-07-19 23:37:45 +02:00
// Clear whole list
for ( int i = 0 ; i < _projects . size ( ) ; + + i ) {
Item & project = _projects . write [ i ] ;
2020-04-02 01:20:12 +02:00
CRASH_COND ( project . control = = nullptr ) ;
2019-07-19 23:37:45 +02:00
memdelete ( project . control ) ; // Why not queue_free()?
2016-07-31 00:04:16 +02:00
}
2019-07-19 23:37:45 +02:00
_projects . clear ( ) ;
_last_clicked = " " ;
2022-02-24 02:19:27 +01:00
_selected_project_paths . clear ( ) ;
2019-07-19 23:37:45 +02:00
2022-02-24 02:19:27 +01:00
List < String > sections ;
_config . load ( _config_path ) ;
_config . get_sections ( & sections ) ;
2019-07-19 23:37:45 +02:00
2022-02-24 02:19:27 +01:00
for ( const String & path : sections ) {
bool favorite = _config . get_value ( path , " favorite " , false ) ;
_projects . push_back ( load_project_data ( path , favorite ) ) ;
2019-07-19 23:37:45 +02:00
}
// Create controls
for ( int i = 0 ; i < _projects . size ( ) ; + + i ) {
2023-04-04 15:27:13 +02:00
_create_project_item_control ( i ) ;
2019-02-11 17:44:23 +01:00
}
2022-10-17 10:12:35 +02:00
sort_projects ( ) ;
2023-04-04 15:27:13 +02:00
_update_icons_async ( ) ;
update_dock_menu ( ) ;
2022-10-17 10:12:35 +02:00
2019-07-19 23:37:45 +02:00
set_v_scroll ( 0 ) ;
2023-12-20 13:09:36 +01:00
emit_signal ( SNAME ( SIGNAL_LIST_CHANGED ) ) ;
2019-10-24 12:25:21 +02:00
}
void ProjectList : : update_dock_menu ( ) {
2020-03-03 14:36:29 +01:00
if ( ! DisplayServer : : get_singleton ( ) - > has_feature ( DisplayServer : : FEATURE_GLOBAL_MENU ) ) {
return ;
}
DisplayServer : : get_singleton ( ) - > global_menu_clear ( " _dock " ) ;
2019-10-24 12:25:21 +02:00
int favs_added = 0 ;
int total_added = 0 ;
for ( int i = 0 ; i < _projects . size ( ) ; + + i ) {
if ( ! _projects [ i ] . grayed & & ! _projects [ i ] . missing ) {
if ( _projects [ i ] . favorite ) {
favs_added + + ;
} else {
if ( favs_added ! = 0 ) {
2020-03-03 14:36:29 +01:00
DisplayServer : : get_singleton ( ) - > global_menu_add_separator ( " _dock " ) ;
2019-10-24 12:25:21 +02:00
}
favs_added = 0 ;
}
2022-08-31 10:11:52 +02:00
DisplayServer : : get_singleton ( ) - > global_menu_add_item ( " _dock " , _projects [ i ] . project_name + " ( " + _projects [ i ] . path + " ) " , callable_mp ( this , & ProjectList : : _global_menu_open_project ) , Callable ( ) , i ) ;
2019-10-24 12:25:21 +02:00
total_added + + ;
}
}
if ( total_added ! = 0 ) {
2020-03-03 14:36:29 +01:00
DisplayServer : : get_singleton ( ) - > global_menu_add_separator ( " _dock " ) ;
2019-10-24 12:25:21 +02:00
}
2020-03-07 17:02:54 +01:00
DisplayServer : : get_singleton ( ) - > global_menu_add_item ( " _dock " , TTR ( " New Window " ) , callable_mp ( this , & ProjectList : : _global_menu_new_window ) ) ;
}
void ProjectList : : _global_menu_new_window ( const Variant & p_tag ) {
List < String > args ;
args . push_back ( " -p " ) ;
2021-11-01 10:12:52 +01:00
OS : : get_singleton ( ) - > create_instance ( args ) ;
2020-03-07 17:02:54 +01:00
}
void ProjectList : : _global_menu_open_project ( const Variant & p_tag ) {
int idx = ( int ) p_tag ;
if ( idx > = 0 & & idx < _projects . size ( ) ) {
2022-08-30 02:34:01 +02:00
String conf = _projects [ idx ] . path . path_join ( " project.godot " ) ;
2020-03-07 17:02:54 +01:00
List < String > args ;
args . push_back ( conf ) ;
2021-11-01 10:12:52 +01:00
OS : : get_singleton ( ) - > create_instance ( args ) ;
2020-03-07 17:02:54 +01:00
}
2016-07-29 02:33:59 +02:00
}
2023-04-04 15:27:13 +02:00
void ProjectList : : _create_project_item_control ( int p_index ) {
2019-07-19 23:37:45 +02:00
// Will be added last in the list, so make sure indexes match
ERR_FAIL_COND ( p_index ! = _scroll_children - > get_child_count ( ) ) ;
2017-05-20 17:38:03 +02:00
2019-07-19 23:37:45 +02:00
Item & item = _projects . write [ p_index ] ;
2020-04-02 01:20:12 +02:00
ERR_FAIL_COND ( item . control ! = nullptr ) ; // Already created
2014-02-10 02:10:30 +01:00
2019-07-19 23:37:45 +02:00
ProjectListItemControl * hb = memnew ( ProjectListItemControl ) ;
2022-02-08 10:14:58 +01:00
hb - > add_theme_constant_override ( " separation " , 10 * EDSCALE ) ;
2023-03-10 17:01:08 +01:00
hb - > set_project_title ( ! item . missing ? item . project_name : TTR ( " Missing Project " ) ) ;
hb - > set_project_path ( item . path ) ;
2022-08-25 12:42:17 +02:00
hb - > set_tooltip_text ( item . description ) ;
2023-03-17 23:30:21 +01:00
hb - > set_tags ( item . tags , this ) ;
2023-07-07 00:37:24 +02:00
hb - > set_unsupported_features ( item . unsupported_features . duplicate ( ) ) ;
2019-07-19 23:37:45 +02:00
hb - > set_is_favorite ( item . favorite ) ;
2023-03-10 17:01:08 +01:00
hb - > set_is_missing ( item . missing ) ;
hb - > set_is_grayed ( item . grayed ) ;
2019-07-19 23:37:45 +02:00
2023-03-10 17:01:08 +01:00
hb - > connect ( " gui_input " , callable_mp ( this , & ProjectList : : _panel_input ) . bind ( hb ) ) ;
hb - > connect ( " favorite_pressed " , callable_mp ( this , & ProjectList : : _favorite_pressed ) . bind ( hb ) ) ;
2021-11-24 17:12:56 +01:00
2023-01-15 22:50:40 +01:00
# if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
2023-03-10 17:01:08 +01:00
hb - > connect ( " explore_pressed " , callable_mp ( this , & ProjectList : : _show_project ) . bind ( item . path ) ) ;
2023-01-15 22:50:40 +01:00
# endif
2019-07-19 23:37:45 +02:00
_scroll_children - > add_child ( hb ) ;
item . control = hb ;
}
2014-05-01 15:47:45 +02:00
2019-07-19 23:37:45 +02:00
void ProjectList : : set_search_term ( String p_search_term ) {
_search_term = p_search_term ;
}
2014-05-01 15:47:45 +02:00
2020-08-27 15:43:49 +02:00
void ProjectList : : set_order_option ( int p_option ) {
2020-08-29 15:39:34 +02:00
FilterOption selected = ( FilterOption ) p_option ;
EditorSettings : : get_singleton ( ) - > set ( " project_manager/sorting_order " , p_option ) ;
EditorSettings : : get_singleton ( ) - > save ( ) ;
_order_option = selected ;
sort_projects ( ) ;
2019-07-19 23:37:45 +02:00
}
2014-05-01 15:47:45 +02:00
2019-07-19 23:37:45 +02:00
void ProjectList : : sort_projects ( ) {
SortArray < Item , ProjectListComparator > sorter ;
sorter . compare . order_option = _order_option ;
sorter . sort ( _projects . ptrw ( ) , _projects . size ( ) ) ;
2014-05-01 15:47:45 +02:00
2023-03-17 23:30:21 +01:00
String search_term ;
PackedStringArray tags ;
if ( ! _search_term . is_empty ( ) ) {
PackedStringArray search_parts = _search_term . split ( " " ) ;
if ( search_parts . size ( ) > 1 | | search_parts [ 0 ] . begins_with ( " tag: " ) ) {
PackedStringArray remaining ;
for ( const String & part : search_parts ) {
if ( part . begins_with ( " tag: " ) ) {
tags . push_back ( part . get_slice ( " : " , 1 ) ) ;
} else {
remaining . append ( part ) ;
}
}
search_term = String ( " " ) . join ( remaining ) ; // Search term without tags.
} else {
search_term = _search_term ;
}
}
2019-07-19 23:37:45 +02:00
for ( int i = 0 ; i < _projects . size ( ) ; + + i ) {
Item & item = _projects . write [ i ] ;
2014-05-01 15:47:45 +02:00
2022-09-29 11:53:28 +02:00
bool item_visible = true ;
2021-12-09 10:42:46 +01:00
if ( ! _search_term . is_empty ( ) ) {
2019-08-13 19:16:17 +02:00
String search_path ;
2023-03-17 23:30:21 +01:00
if ( search_term . contains ( " / " ) ) {
2019-08-13 19:16:17 +02:00
// Search path will match the whole path
search_path = item . path ;
} else {
// Search path will only match the last path component to make searching more strict
search_path = item . path . get_file ( ) ;
2014-05-01 15:47:45 +02:00
}
2019-08-13 19:16:17 +02:00
2023-03-17 23:30:21 +01:00
bool missing_tags = false ;
for ( const String & tag : tags ) {
if ( ! item . tags . has ( tag ) ) {
missing_tags = true ;
break ;
}
}
// When searching, display projects whose name or path contain the search term and whose tags match the searched tags.
item_visible = ! missing_tags & & ( search_term . is_empty ( ) | | item . project_name . findn ( search_term ) ! = - 1 | | search_path . findn ( search_term ) ! = - 1 ) ;
2014-05-01 15:47:45 +02:00
}
2022-09-29 11:53:28 +02:00
item . control - > set_visible ( item_visible ) ;
2019-07-19 23:37:45 +02:00
}
2014-02-10 02:10:30 +01:00
2019-07-19 23:37:45 +02:00
for ( int i = 0 ; i < _projects . size ( ) ; + + i ) {
Item & item = _projects . write [ i ] ;
2019-12-30 18:04:29 +01:00
item . control - > get_parent ( ) - > move_child ( item . control , i ) ;
2014-02-10 02:10:30 +01:00
}
2019-07-19 23:37:45 +02:00
// Rewind the coroutine because order of projects changed
2023-04-04 15:27:13 +02:00
_update_icons_async ( ) ;
2019-10-24 12:25:21 +02:00
update_dock_menu ( ) ;
2014-02-10 02:10:30 +01:00
}
2022-05-19 17:00:06 +02:00
const HashSet < String > & ProjectList : : get_selected_project_keys ( ) const {
2019-07-19 23:37:45 +02:00
// Faster if that's all you need
2022-02-24 02:19:27 +01:00
return _selected_project_paths ;
2019-07-19 23:37:45 +02:00
}
2016-07-23 21:37:25 +02:00
2019-07-19 23:37:45 +02:00
Vector < ProjectList : : Item > ProjectList : : get_selected_projects ( ) const {
Vector < Item > items ;
2022-02-24 02:19:27 +01:00
if ( _selected_project_paths . size ( ) = = 0 ) {
2019-07-19 23:37:45 +02:00
return items ;
}
2022-02-24 02:19:27 +01:00
items . resize ( _selected_project_paths . size ( ) ) ;
2019-07-19 23:37:45 +02:00
int j = 0 ;
for ( int i = 0 ; i < _projects . size ( ) ; + + i ) {
const Item & item = _projects [ i ] ;
2022-02-24 02:19:27 +01:00
if ( _selected_project_paths . has ( item . path ) ) {
2019-07-19 23:37:45 +02:00
items . write [ j + + ] = item ;
}
}
ERR_FAIL_COND_V ( j ! = items . size ( ) , items ) ;
return items ;
}
2016-07-23 21:37:25 +02:00
2019-07-19 23:37:45 +02:00
void ProjectList : : ensure_project_visible ( int p_index ) {
const Item & item = _projects [ p_index ] ;
2021-05-15 05:19:23 +02:00
ensure_control_visible ( item . control ) ;
2019-07-19 23:37:45 +02:00
}
2016-07-23 21:37:25 +02:00
2019-07-19 23:37:45 +02:00
int ProjectList : : get_single_selected_index ( ) const {
2022-02-24 02:19:27 +01:00
if ( _selected_project_paths . size ( ) = = 0 ) {
2019-07-19 23:37:45 +02:00
// Default selection
return 0 ;
}
String key ;
2022-02-24 02:19:27 +01:00
if ( _selected_project_paths . size ( ) = = 1 ) {
2019-07-19 23:37:45 +02:00
// Only one selected
2022-02-24 02:19:27 +01:00
key = * _selected_project_paths . begin ( ) ;
2019-07-19 23:37:45 +02:00
} else {
// Multiple selected, consider the last clicked one as "main"
key = _last_clicked ;
}
for ( int i = 0 ; i < _projects . size ( ) ; + + i ) {
2022-02-24 02:19:27 +01:00
if ( _projects [ i ] . path = = key ) {
2019-07-19 23:37:45 +02:00
return i ;
}
}
return 0 ;
}
2016-07-23 21:37:25 +02:00
2023-04-04 15:27:13 +02:00
void ProjectList : : _remove_project ( int p_index , bool p_update_config ) {
2019-07-19 23:37:45 +02:00
const Item item = _projects [ p_index ] ; // Take a copy
2016-08-05 16:06:16 +02:00
2022-02-24 02:19:27 +01:00
_selected_project_paths . erase ( item . path ) ;
2018-10-02 11:28:18 +02:00
2022-02-24 02:19:27 +01:00
if ( _last_clicked = = item . path ) {
2019-07-19 23:37:45 +02:00
_last_clicked = " " ;
}
2016-07-23 21:37:25 +02:00
2019-07-19 23:37:45 +02:00
memdelete ( item . control ) ;
2021-07-04 00:17:03 +02:00
_projects . remove_at ( p_index ) ;
2016-07-23 21:37:25 +02:00
2022-02-24 02:19:27 +01:00
if ( p_update_config ) {
_config . erase_section ( item . path ) ;
2019-07-19 23:37:45 +02:00
// Not actually saving the file, in case you are doing more changes to settings
}
2019-10-24 12:25:21 +02:00
update_dock_menu ( ) ;
2019-07-19 23:37:45 +02:00
}
2016-07-23 21:37:25 +02:00
2019-07-19 23:37:45 +02:00
bool ProjectList : : is_any_project_missing ( ) const {
for ( int i = 0 ; i < _projects . size ( ) ; + + i ) {
if ( _projects [ i ] . missing ) {
return true ;
}
}
return false ;
}
2016-07-23 21:37:25 +02:00
2019-07-19 23:37:45 +02:00
void ProjectList : : erase_missing_projects ( ) {
2020-12-15 13:04:21 +01:00
if ( _projects . is_empty ( ) ) {
2019-07-19 23:37:45 +02:00
return ;
}
2016-07-23 21:37:25 +02:00
2019-07-19 23:37:45 +02:00
int deleted_count = 0 ;
int remaining_count = 0 ;
2016-07-23 21:37:25 +02:00
2019-07-19 23:37:45 +02:00
for ( int i = 0 ; i < _projects . size ( ) ; + + i ) {
const Item & item = _projects [ i ] ;
if ( item . missing ) {
2023-04-04 15:27:13 +02:00
_remove_project ( i , true ) ;
2019-07-19 23:37:45 +02:00
- - i ;
+ + deleted_count ;
2016-07-23 21:37:25 +02:00
2019-07-19 23:37:45 +02:00
} else {
+ + remaining_count ;
}
}
2016-07-23 21:37:25 +02:00
2019-07-19 23:37:45 +02:00
print_line ( " Removed " + itos ( deleted_count ) + " projects from the list, remaining " + itos ( remaining_count ) + " projects " ) ;
2022-02-24 02:19:27 +01:00
save_config ( ) ;
2019-07-19 23:37:45 +02:00
}
2016-07-23 21:37:25 +02:00
2023-12-20 13:09:36 +01:00
void ProjectList : : _scan_folder_recursive ( const String & p_path , List < String > * r_projects ) {
Ref < DirAccess > da = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
Error error = da - > change_dir ( p_path ) ;
ERR_FAIL_COND_MSG ( error ! = OK , vformat ( " Failed to open the path \" %s \" for scanning (code %d). " , p_path , error ) ) ;
da - > list_dir_begin ( ) ;
String n = da - > get_next ( ) ;
while ( ! n . is_empty ( ) ) {
if ( da - > current_is_dir ( ) & & n [ 0 ] ! = ' . ' ) {
_scan_folder_recursive ( da - > get_current_dir ( ) . path_join ( n ) , r_projects ) ;
} else if ( n = = " project.godot " ) {
r_projects - > push_back ( da - > get_current_dir ( ) ) ;
}
n = da - > get_next ( ) ;
}
da - > list_dir_end ( ) ;
}
void ProjectList : : find_projects ( const String & p_path ) {
PackedStringArray paths = { p_path } ;
find_projects_multiple ( paths ) ;
}
void ProjectList : : find_projects_multiple ( const PackedStringArray & p_paths ) {
List < String > projects ;
for ( int i = 0 ; i < p_paths . size ( ) ; i + + ) {
const String & base_path = p_paths . get ( i ) ;
print_verbose ( vformat ( " Scanning for projects in \" %s \" . " , base_path ) ) ;
_scan_folder_recursive ( base_path , & projects ) ;
print_verbose ( vformat ( " Found %d project(s). " , projects . size ( ) ) ) ;
}
for ( const String & E : projects ) {
add_project ( E , false ) ;
}
save_config ( ) ;
update_project_list ( ) ;
}
2019-07-19 23:37:45 +02:00
int ProjectList : : refresh_project ( const String & dir_path ) {
2022-02-24 02:19:27 +01:00
// Reloads information about a specific project.
2019-07-19 23:37:45 +02:00
// If it wasn't loaded and should be in the list, it is added (i.e new project).
// If it isn't in the list anymore, it is removed.
// If it is in the list but doesn't exist anymore, it is marked as missing.
2016-07-23 21:37:25 +02:00
2022-02-24 02:19:27 +01:00
bool should_be_in_list = _config . has_section ( dir_path ) ;
bool is_favourite = _config . get_value ( dir_path , " favorite " , false ) ;
2016-07-23 21:37:25 +02:00
2022-02-24 02:19:27 +01:00
bool was_selected = _selected_project_paths . has ( dir_path ) ;
2016-07-23 21:37:25 +02:00
2019-07-19 23:37:45 +02:00
// Remove item in any case
for ( int i = 0 ; i < _projects . size ( ) ; + + i ) {
const Item & existing_item = _projects [ i ] ;
if ( existing_item . path = = dir_path ) {
2023-04-04 15:27:13 +02:00
_remove_project ( i , false ) ;
2019-07-19 23:37:45 +02:00
break ;
}
}
2016-08-05 16:06:16 +02:00
2019-07-19 23:37:45 +02:00
int index = - 1 ;
if ( should_be_in_list ) {
// Recreate it with updated info
2016-07-23 21:37:25 +02:00
2022-02-24 02:19:27 +01:00
Item item = load_project_data ( dir_path , is_favourite ) ;
2016-07-23 21:37:25 +02:00
2019-07-19 23:37:45 +02:00
_projects . push_back ( item ) ;
2023-04-04 15:27:13 +02:00
_create_project_item_control ( _projects . size ( ) - 1 ) ;
2016-07-23 21:37:25 +02:00
2019-07-19 23:37:45 +02:00
sort_projects ( ) ;
for ( int i = 0 ; i < _projects . size ( ) ; + + i ) {
2022-02-24 02:19:27 +01:00
if ( _projects [ i ] . path = = dir_path ) {
2019-07-19 23:37:45 +02:00
if ( was_selected ) {
select_project ( i ) ;
ensure_project_visible ( i ) ;
2016-07-23 21:37:25 +02:00
}
2023-04-04 15:27:13 +02:00
_load_project_icon ( i ) ;
2019-10-24 12:25:21 +02:00
2019-07-19 23:37:45 +02:00
index = i ;
break ;
2016-07-23 21:37:25 +02:00
}
2019-07-19 23:37:45 +02:00
}
}
2016-07-23 21:37:25 +02:00
2019-07-19 23:37:45 +02:00
return index ;
}
2016-07-23 21:37:25 +02:00
2022-02-24 02:19:27 +01:00
void ProjectList : : add_project ( const String & dir_path , bool favorite ) {
if ( ! _config . has_section ( dir_path ) ) {
_config . set_value ( dir_path , " favorite " , favorite ) ;
}
}
void ProjectList : : save_config ( ) {
_config . save ( _config_path ) ;
}
2022-08-26 15:22:30 +02:00
void ProjectList : : set_project_version ( const String & p_project_path , int p_version ) {
for ( ProjectList : : Item & E : _projects ) {
if ( E . path = = p_project_path ) {
E . version = p_version ;
break ;
}
}
}
2019-07-19 23:37:45 +02:00
int ProjectList : : get_project_count ( ) const {
return _projects . size ( ) ;
}
2016-07-23 21:37:25 +02:00
2023-04-04 15:27:13 +02:00
void ProjectList : : _clear_project_selection ( ) {
2019-07-19 23:37:45 +02:00
Vector < Item > previous_selected_items = get_selected_projects ( ) ;
2022-02-24 02:19:27 +01:00
_selected_project_paths . clear ( ) ;
2016-07-23 21:37:25 +02:00
2019-07-19 23:37:45 +02:00
for ( int i = 0 ; i < previous_selected_items . size ( ) ; + + i ) {
2023-04-04 15:27:13 +02:00
previous_selected_items [ i ] . control - > set_selected ( false ) ;
2019-07-19 23:37:45 +02:00
}
2023-04-04 15:27:13 +02:00
}
2016-07-23 21:37:25 +02:00
2023-04-04 15:27:13 +02:00
void ProjectList : : _toggle_project ( int p_index ) {
// This methods adds to the selection or removes from the
// selection.
Item & item = _projects . write [ p_index ] ;
if ( _selected_project_paths . has ( item . path ) ) {
_deselect_project_nocheck ( p_index ) ;
} else {
_select_project_nocheck ( p_index ) ;
}
}
void ProjectList : : _select_project_nocheck ( int p_index ) {
Item & item = _projects . write [ p_index ] ;
_selected_project_paths . insert ( item . path ) ;
item . control - > set_selected ( true ) ;
}
void ProjectList : : _deselect_project_nocheck ( int p_index ) {
Item & item = _projects . write [ p_index ] ;
_selected_project_paths . erase ( item . path ) ;
item . control - > set_selected ( false ) ;
}
void ProjectList : : select_project ( int p_index ) {
// This method keeps only one project selected.
_clear_project_selection ( ) ;
_select_project_nocheck ( p_index ) ;
2019-07-19 23:37:45 +02:00
}
2016-07-23 21:37:25 +02:00
2020-01-17 22:07:11 +01:00
void ProjectList : : select_first_visible_project ( ) {
2023-04-04 15:27:13 +02:00
_clear_project_selection ( ) ;
2020-01-17 22:07:11 +01:00
for ( int i = 0 ; i < _projects . size ( ) ; i + + ) {
if ( _projects [ i ] . control - > is_visible ( ) ) {
2023-04-04 15:27:13 +02:00
_select_project_nocheck ( i ) ;
2020-01-17 22:07:11 +01:00
break ;
}
}
}
2023-04-04 15:27:13 +02:00
inline void _sort_project_range ( int & a , int & b ) {
2019-07-19 23:37:45 +02:00
if ( a > b ) {
int temp = a ;
a = b ;
b = temp ;
}
}
2016-07-23 21:37:25 +02:00
2023-04-04 15:27:13 +02:00
void ProjectList : : _select_project_range ( int p_begin , int p_end ) {
_clear_project_selection ( ) ;
2016-07-23 21:37:25 +02:00
2023-04-04 15:27:13 +02:00
_sort_project_range ( p_begin , p_end ) ;
for ( int i = p_begin ; i < = p_end ; + + i ) {
_select_project_nocheck ( i ) ;
2019-07-19 23:37:45 +02:00
}
}
2016-08-05 16:06:16 +02:00
2021-03-20 19:31:49 +01:00
void ProjectList : : erase_selected_projects ( bool p_delete_project_contents ) {
2022-02-24 02:19:27 +01:00
if ( _selected_project_paths . size ( ) = = 0 ) {
2019-07-19 23:37:45 +02:00
return ;
}
2016-07-23 21:37:25 +02:00
2019-07-19 23:37:45 +02:00
for ( int i = 0 ; i < _projects . size ( ) ; + + i ) {
Item & item = _projects . write [ i ] ;
2022-02-24 02:19:27 +01:00
if ( _selected_project_paths . has ( item . path ) & & item . control - > is_visible ( ) ) {
_config . erase_section ( item . path ) ;
2019-07-19 23:37:45 +02:00
2023-03-15 23:48:52 +01:00
// Comment out for now until we have a better warning system to
// ensure users delete their project only.
//if (p_delete_project_contents) {
// OS::get_singleton()->move_to_trash(item.path);
//}
2021-03-20 19:31:49 +01:00
2019-07-19 23:37:45 +02:00
memdelete ( item . control ) ;
2021-07-04 00:17:03 +02:00
_projects . remove_at ( i ) ;
2019-07-19 23:37:45 +02:00
- - i ;
}
}
2022-02-24 02:19:27 +01:00
save_config ( ) ;
_selected_project_paths . clear ( ) ;
2019-07-19 23:37:45 +02:00
_last_clicked = " " ;
2019-10-24 12:25:21 +02:00
update_dock_menu ( ) ;
2019-07-19 23:37:45 +02:00
}
2023-03-10 13:57:32 +01:00
// Input for each item in the list.
2019-07-19 23:37:45 +02:00
void ProjectList : : _panel_input ( const Ref < InputEvent > & p_ev , Node * p_hb ) {
Ref < InputEventMouseButton > mb = p_ev ;
int clicked_index = p_hb - > get_index ( ) ;
const Item & clicked_project = _projects [ clicked_index ] ;
2021-08-13 23:31:57 +02:00
if ( mb . is_valid ( ) & & mb - > is_pressed ( ) & & mb - > get_button_index ( ) = = MouseButton : : LEFT ) {
2022-02-24 02:19:27 +01:00
if ( mb - > is_shift_pressed ( ) & & _selected_project_paths . size ( ) > 0 & & ! _last_clicked . is_empty ( ) & & clicked_project . path ! = _last_clicked ) {
2019-07-19 23:37:45 +02:00
int anchor_index = - 1 ;
for ( int i = 0 ; i < _projects . size ( ) ; + + i ) {
const Item & p = _projects [ i ] ;
2022-02-24 02:19:27 +01:00
if ( p . path = = _last_clicked ) {
2019-07-19 23:37:45 +02:00
anchor_index = p . control - > get_index ( ) ;
break ;
2016-07-23 21:37:25 +02:00
}
2019-07-19 23:37:45 +02:00
}
CRASH_COND ( anchor_index = = - 1 ) ;
2023-04-04 15:27:13 +02:00
_select_project_range ( anchor_index , clicked_index ) ;
2016-07-23 21:37:25 +02:00
2023-06-08 23:24:00 +02:00
} else if ( mb - > is_command_or_control_pressed ( ) ) {
2023-04-04 15:27:13 +02:00
_toggle_project ( clicked_index ) ;
2019-07-19 23:37:45 +02:00
} else {
2022-02-24 02:19:27 +01:00
_last_clicked = clicked_project . path ;
2019-07-19 23:37:45 +02:00
select_project ( clicked_index ) ;
2016-07-23 21:37:25 +02:00
}
2021-07-17 23:22:52 +02:00
emit_signal ( SNAME ( SIGNAL_SELECTION_CHANGED ) ) ;
2019-07-19 23:37:45 +02:00
2022-03-04 19:18:40 +01:00
// Do not allow opening a project more than once using a single project manager instance.
// Opening the same project in several editor instances at once can lead to various issues.
2023-06-08 23:24:00 +02:00
if ( ! mb - > is_command_or_control_pressed ( ) & & mb - > is_double_click ( ) & & ! project_opening_initiated ) {
2021-07-17 23:22:52 +02:00
emit_signal ( SNAME ( SIGNAL_PROJECT_ASK_OPEN ) ) ;
2016-07-23 21:37:25 +02:00
}
}
}
2019-07-19 23:37:45 +02:00
void ProjectList : : _favorite_pressed ( Node * p_hb ) {
ProjectListItemControl * control = Object : : cast_to < ProjectListItemControl > ( p_hb ) ;
int index = control - > get_index ( ) ;
Item item = _projects . write [ index ] ; // Take copy
2014-05-20 10:38:18 +02:00
2019-07-19 23:37:45 +02:00
item . favorite = ! item . favorite ;
2014-05-20 10:38:18 +02:00
2022-02-24 02:19:27 +01:00
_config . set_value ( item . path , " favorite " , item . favorite ) ;
save_config ( ) ;
2014-05-20 10:38:18 +02:00
2019-07-19 23:37:45 +02:00
_projects . write [ index ] = item ;
control - > set_is_favorite ( item . favorite ) ;
2014-02-10 02:10:30 +01:00
2019-07-19 23:37:45 +02:00
sort_projects ( ) ;
2014-06-11 11:57:13 +02:00
2019-07-19 23:37:45 +02:00
if ( item . favorite ) {
for ( int i = 0 ; i < _projects . size ( ) ; + + i ) {
2022-02-24 02:19:27 +01:00
if ( _projects [ i ] . path = = item . path ) {
2019-07-19 23:37:45 +02:00
ensure_project_visible ( i ) ;
break ;
}
}
2014-02-10 02:10:30 +01:00
}
2019-10-24 12:25:21 +02:00
update_dock_menu ( ) ;
2019-07-19 23:37:45 +02:00
}
2014-02-10 02:10:30 +01:00
2019-07-19 23:37:45 +02:00
void ProjectList : : _show_project ( const String & p_path ) {
2022-12-07 04:33:35 +01:00
OS : : get_singleton ( ) - > shell_show_in_file_manager ( p_path , true ) ;
2019-07-19 23:37:45 +02:00
}
2014-02-10 02:10:30 +01:00
2019-07-19 23:37:45 +02:00
void ProjectList : : _bind_methods ( ) {
2023-12-20 13:09:36 +01:00
ADD_SIGNAL ( MethodInfo ( SIGNAL_LIST_CHANGED ) ) ;
2019-07-19 23:37:45 +02:00
ADD_SIGNAL ( MethodInfo ( SIGNAL_SELECTION_CHANGED ) ) ;
ADD_SIGNAL ( MethodInfo ( SIGNAL_PROJECT_ASK_OPEN ) ) ;
}
2014-02-10 02:10:30 +01:00
2023-03-10 13:57:32 +01:00
ProjectList : : ProjectList ( ) {
_scroll_children = memnew ( VBoxContainer ) ;
_scroll_children - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
add_child ( _scroll_children ) ;
_config_path = EditorPaths : : get_singleton ( ) - > get_data_dir ( ) . path_join ( " projects.cfg " ) ;
2023-12-20 13:09:36 +01:00
_migrate_config ( ) ;
2023-03-10 13:57:32 +01:00
}
/// Project Manager.
2022-02-12 20:55:11 +01:00
ProjectManager * ProjectManager : : singleton = nullptr ;
2019-07-19 23:37:45 +02:00
void ProjectManager : : _notification ( int p_what ) {
switch ( p_what ) {
2020-09-03 13:22:16 +02:00
case NOTIFICATION_TRANSLATION_CHANGED :
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED : {
2020-12-22 17:24:29 +01:00
settings_hb - > set_anchors_and_offsets_preset ( Control : : PRESET_TOP_RIGHT ) ;
2022-08-13 23:21:24 +02:00
queue_redraw ( ) ;
2020-09-03 13:22:16 +02:00
} break ;
2022-02-16 15:17:55 +01:00
2019-07-19 23:37:45 +02:00
case NOTIFICATION_ENTER_TREE : {
2023-03-10 15:22:33 +01:00
Engine : : get_singleton ( ) - > set_editor_hint ( false ) ;
} break ;
case NOTIFICATION_THEME_CHANGED : {
2023-08-13 02:33:39 +02:00
background_panel - > add_theme_style_override ( " panel " , get_theme_stylebox ( SNAME ( " Background " ) , EditorStringName ( EditorStyles ) ) ) ;
loading_label - > add_theme_font_override ( " font " , get_theme_font ( SNAME ( " bold " ) , EditorStringName ( EditorFonts ) ) ) ;
2023-03-10 15:22:33 +01:00
search_panel - > add_theme_style_override ( " panel " , get_theme_stylebox ( SNAME ( " search_panel " ) , SNAME ( " ProjectManager " ) ) ) ;
// Top bar.
2023-08-13 02:33:39 +02:00
search_box - > set_right_icon ( get_editor_theme_icon ( SNAME ( " Search " ) ) ) ;
language_btn - > set_icon ( get_editor_theme_icon ( SNAME ( " Environment " ) ) ) ;
2020-08-29 15:39:34 +02:00
2023-03-10 15:22:33 +01:00
// Sidebar.
2023-08-13 02:33:39 +02:00
create_btn - > set_icon ( get_editor_theme_icon ( SNAME ( " Add " ) ) ) ;
import_btn - > set_icon ( get_editor_theme_icon ( SNAME ( " Load " ) ) ) ;
scan_btn - > set_icon ( get_editor_theme_icon ( SNAME ( " Search " ) ) ) ;
open_btn - > set_icon ( get_editor_theme_icon ( SNAME ( " Edit " ) ) ) ;
run_btn - > set_icon ( get_editor_theme_icon ( SNAME ( " Play " ) ) ) ;
rename_btn - > set_icon ( get_editor_theme_icon ( SNAME ( " Rename " ) ) ) ;
manage_tags_btn - > set_icon ( get_editor_theme_icon ( " Script " ) ) ;
erase_btn - > set_icon ( get_editor_theme_icon ( SNAME ( " Remove " ) ) ) ;
erase_missing_btn - > set_icon ( get_editor_theme_icon ( SNAME ( " Clear " ) ) ) ;
create_tag_btn - > set_icon ( get_editor_theme_icon ( " Add " ) ) ;
tag_error - > add_theme_color_override ( " font_color " , get_theme_color ( " error_color " , EditorStringName ( Editor ) ) ) ;
tag_edit_error - > add_theme_color_override ( " font_color " , get_theme_color ( " error_color " , EditorStringName ( Editor ) ) ) ;
2022-06-15 09:04:48 +02:00
2023-03-10 15:22:33 +01:00
create_btn - > add_theme_constant_override ( " h_separation " , get_theme_constant ( SNAME ( " sidebar_button_icon_separation " ) , SNAME ( " ProjectManager " ) ) ) ;
import_btn - > add_theme_constant_override ( " h_separation " , get_theme_constant ( SNAME ( " sidebar_button_icon_separation " ) , SNAME ( " ProjectManager " ) ) ) ;
scan_btn - > add_theme_constant_override ( " h_separation " , get_theme_constant ( SNAME ( " sidebar_button_icon_separation " ) , SNAME ( " ProjectManager " ) ) ) ;
open_btn - > add_theme_constant_override ( " h_separation " , get_theme_constant ( SNAME ( " sidebar_button_icon_separation " ) , SNAME ( " ProjectManager " ) ) ) ;
run_btn - > add_theme_constant_override ( " h_separation " , get_theme_constant ( SNAME ( " sidebar_button_icon_separation " ) , SNAME ( " ProjectManager " ) ) ) ;
rename_btn - > add_theme_constant_override ( " h_separation " , get_theme_constant ( SNAME ( " sidebar_button_icon_separation " ) , SNAME ( " ProjectManager " ) ) ) ;
2023-03-17 23:30:21 +01:00
manage_tags_btn - > add_theme_constant_override ( " h_separation " , get_theme_constant ( SNAME ( " sidebar_button_icon_separation " ) , SNAME ( " ProjectManager " ) ) ) ;
2023-03-10 15:22:33 +01:00
erase_btn - > add_theme_constant_override ( " h_separation " , get_theme_constant ( SNAME ( " sidebar_button_icon_separation " ) , SNAME ( " ProjectManager " ) ) ) ;
erase_missing_btn - > add_theme_constant_override ( " h_separation " , get_theme_constant ( SNAME ( " sidebar_button_icon_separation " ) , SNAME ( " ProjectManager " ) ) ) ;
// Asset library popup.
if ( asset_library ) {
// Removes extra border margins.
asset_library - > add_theme_style_override ( " panel " , memnew ( StyleBoxEmpty ) ) ;
}
2019-07-19 23:37:45 +02:00
} break ;
2022-02-16 15:17:55 +01:00
2019-10-01 12:10:58 +02:00
case NOTIFICATION_RESIZED : {
2022-05-25 13:09:53 +02:00
if ( open_templates & & open_templates - > is_visible ( ) ) {
2020-03-06 18:00:16 +01:00
open_templates - > popup_centered ( ) ;
2019-10-01 12:10:58 +02:00
}
2022-05-25 13:09:53 +02:00
if ( asset_library ) {
real_t size = get_size ( ) . x / EDSCALE ;
// Adjust names of tabs to fit the new size.
if ( size < 650 ) {
2023-07-25 18:58:14 +02:00
local_projects_vb - > set_name ( TTR ( " Local " ) ) ;
2022-05-25 13:09:53 +02:00
asset_library - > set_name ( TTR ( " Asset Library " ) ) ;
} else {
2023-07-25 18:58:14 +02:00
local_projects_vb - > set_name ( TTR ( " Local Projects " ) ) ;
2022-05-25 13:09:53 +02:00
asset_library - > set_name ( TTR ( " Asset Library Projects " ) ) ;
}
2022-05-20 06:57:10 +02:00
}
2019-10-01 12:10:58 +02:00
} break ;
2022-02-16 15:17:55 +01:00
2019-07-19 23:37:45 +02:00
case NOTIFICATION_READY : {
2022-10-18 16:43:37 +02:00
int default_sorting = ( int ) EDITOR_GET ( " project_manager/sorting_order " ) ;
2020-08-29 15:39:34 +02:00
filter_option - > select ( default_sorting ) ;
_project_list - > set_order_option ( default_sorting ) ;
2022-09-06 07:13:03 +02:00
# ifndef ANDROID_ENABLED
2019-09-23 02:50:50 +02:00
if ( _project_list - > get_project_count ( ) > = 1 ) {
// Focus on the search box immediately to allow the user
// to search without having to reach for their mouse
2020-08-27 15:43:49 +02:00
search_box - > grab_focus ( ) ;
2019-09-23 02:50:50 +02:00
}
2022-09-06 07:13:03 +02:00
# endif
2022-03-16 00:40:46 +01:00
2023-03-10 15:22:33 +01:00
// Suggest browsing asset library to get templates/demos.
if ( asset_library & & open_templates & & _project_list - > get_project_count ( ) = = 0 ) {
open_templates - > popup_centered ( ) ;
2022-03-16 00:40:46 +01:00
}
2019-07-19 23:37:45 +02:00
} break ;
2022-02-16 15:17:55 +01:00
2019-07-19 23:37:45 +02:00
case NOTIFICATION_VISIBILITY_CHANGED : {
2022-01-11 14:59:52 +01:00
set_process_shortcut_input ( is_visible_in_tree ( ) ) ;
2019-07-19 23:37:45 +02:00
} break ;
2022-02-16 15:17:55 +01:00
2020-03-04 17:36:09 +01:00
case NOTIFICATION_WM_CLOSE_REQUEST : {
2019-07-19 23:37:45 +02:00
_dim_window ( ) ;
} break ;
2022-02-16 15:17:55 +01:00
2021-04-08 01:05:47 +02:00
case NOTIFICATION_WM_ABOUT : {
_show_about ( ) ;
} break ;
2019-07-19 23:37:45 +02:00
}
}
2019-06-18 01:37:06 +02:00
2022-02-03 01:21:52 +01:00
Ref < Texture2D > ProjectManager : : _file_dialog_get_icon ( const String & p_path ) {
2023-02-17 22:37:38 +01:00
if ( p_path . get_extension ( ) . to_lower ( ) = = " godot " ) {
return singleton - > icon_type_cache [ " GodotMonochrome " ] ;
}
return singleton - > icon_type_cache [ " Object " ] ;
}
Ref < Texture2D > ProjectManager : : _file_dialog_get_thumbnail ( const String & p_path ) {
if ( p_path . get_extension ( ) . to_lower ( ) = = " godot " ) {
return singleton - > icon_type_cache [ " GodotFile " ] ;
}
return Ref < Texture2D > ( ) ;
2022-02-03 01:21:52 +01:00
}
void ProjectManager : : _build_icon_type_cache ( Ref < Theme > p_theme ) {
2023-01-13 20:24:12 +01:00
if ( p_theme . is_null ( ) ) {
return ;
}
2022-02-03 01:21:52 +01:00
List < StringName > tl ;
2023-08-13 02:33:39 +02:00
p_theme - > get_icon_list ( EditorStringName ( EditorIcons ) , & tl ) ;
2022-02-03 01:21:52 +01:00
for ( List < StringName > : : Element * E = tl . front ( ) ; E ; E = E - > next ( ) ) {
2023-08-13 02:33:39 +02:00
icon_type_cache [ E - > get ( ) ] = p_theme - > get_icon ( E - > get ( ) , EditorStringName ( EditorIcons ) ) ;
2022-02-03 01:21:52 +01:00
}
}
2023-12-20 13:19:21 +01:00
void ProjectManager : : _update_size_limits ( ) {
const Size2 minimum_size = Size2 ( 680 , 450 ) * EDSCALE ;
const Size2 default_size = Size2 ( 1024 , 600 ) * EDSCALE ;
// Define a minimum window size to prevent UI elements from overlapping or being cut off.
Window * w = Object : : cast_to < Window > ( SceneTree : : get_singleton ( ) - > get_root ( ) ) ;
if ( w ) {
// Calling Window methods this early doesn't sync properties with DS.
w - > set_min_size ( minimum_size ) ;
DisplayServer : : get_singleton ( ) - > window_set_min_size ( minimum_size ) ;
w - > set_size ( default_size ) ;
DisplayServer : : get_singleton ( ) - > window_set_size ( default_size ) ;
}
Rect2i screen_rect = DisplayServer : : get_singleton ( ) - > screen_get_usable_rect ( DisplayServer : : get_singleton ( ) - > window_get_current_screen ( ) ) ;
if ( screen_rect . size ! = Vector2i ( ) ) {
// Center the window on the screen.
Vector2i window_position ;
window_position . x = screen_rect . position . x + ( screen_rect . size . x - default_size . x ) / 2 ;
window_position . y = screen_rect . position . y + ( screen_rect . size . y - default_size . y ) / 2 ;
DisplayServer : : get_singleton ( ) - > window_set_position ( window_position ) ;
// Limit popup menus to prevent unusably long lists.
// We try to set it to half the screen resolution, but no smaller than the minimum window size.
Size2 half_screen_rect = ( screen_rect . size * EDSCALE ) / 2 ;
Size2 maximum_popup_size = MAX ( half_screen_rect , minimum_size ) ;
language_btn - > get_popup ( ) - > set_max_size ( maximum_popup_size ) ;
}
}
2019-07-19 23:37:45 +02:00
void ProjectManager : : _dim_window ( ) {
// This method must be called before calling `get_tree()->quit()`.
// Otherwise, its effect won't be visible
2019-06-18 01:37:06 +02:00
2019-07-19 23:37:45 +02:00
// Dim the project manager window while it's quitting to make it clearer that it's busy.
// No transition is applied, as the effect needs to be visible immediately
2019-08-29 15:51:23 +02:00
float c = 0.5f ;
2019-07-19 23:37:45 +02:00
Color dim_color = Color ( c , c , c ) ;
2020-08-27 15:43:49 +02:00
set_modulate ( dim_color ) ;
2019-07-19 23:37:45 +02:00
}
2014-05-01 13:30:10 +02:00
2019-07-19 23:37:45 +02:00
void ProjectManager : : _update_project_buttons ( ) {
Vector < ProjectList : : Item > selected_projects = _project_list - > get_selected_projects ( ) ;
2020-12-15 13:04:21 +01:00
bool empty_selection = selected_projects . is_empty ( ) ;
2014-05-20 10:38:18 +02:00
2019-08-21 22:35:59 +02:00
bool is_missing_project_selected = false ;
for ( int i = 0 ; i < selected_projects . size ( ) ; + + i ) {
if ( selected_projects [ i ] . missing ) {
is_missing_project_selected = true ;
break ;
}
}
2019-07-19 23:37:45 +02:00
erase_btn - > set_disabled ( empty_selection ) ;
2019-08-21 22:35:59 +02:00
open_btn - > set_disabled ( empty_selection | | is_missing_project_selected ) ;
rename_btn - > set_disabled ( empty_selection | | is_missing_project_selected ) ;
2023-03-17 23:30:21 +01:00
manage_tags_btn - > set_disabled ( empty_selection | | is_missing_project_selected | | selected_projects . size ( ) > 1 ) ;
2019-08-21 22:35:59 +02:00
run_btn - > set_disabled ( empty_selection | | is_missing_project_selected ) ;
2014-05-20 10:38:18 +02:00
2020-03-23 08:38:21 +01:00
erase_missing_btn - > set_disabled ( ! _project_list - > is_any_project_missing ( ) ) ;
2019-07-19 23:37:45 +02:00
}
2014-05-01 13:30:10 +02:00
2022-01-11 14:59:52 +01:00
void ProjectManager : : shortcut_input ( const Ref < InputEvent > & p_ev ) {
2021-04-05 08:52:21 +02:00
ERR_FAIL_COND ( p_ev . is_null ( ) ) ;
2019-07-19 23:37:45 +02:00
Ref < InputEventKey > k = p_ev ;
2014-05-01 13:30:10 +02:00
2019-07-19 23:37:45 +02:00
if ( k . is_valid ( ) ) {
2019-08-13 19:16:17 +02:00
if ( ! k - > is_pressed ( ) ) {
2019-07-19 23:37:45 +02:00
return ;
2019-08-13 19:16:17 +02:00
}
// Pressing Command + Q quits the Project Manager
// This is handled by the platform implementation on macOS,
// so only define the shortcut on other platforms
2022-07-20 08:28:22 +02:00
# ifndef MACOS_ENABLED
2022-09-02 11:37:48 +02:00
if ( k - > get_keycode_with_modifiers ( ) = = ( KeyModifierMask : : META | Key : : Q ) ) {
2019-08-13 19:16:17 +02:00
_dim_window ( ) ;
get_tree ( ) - > quit ( ) ;
}
# endif
2019-07-19 23:37:45 +02:00
2020-05-14 16:41:43 +02:00
if ( tabs - > get_current_tab ( ) ! = 0 ) {
2019-07-19 23:37:45 +02:00
return ;
2020-05-14 16:41:43 +02:00
}
2019-07-19 23:37:45 +02:00
2018-04-05 19:59:35 +02:00
bool keycode_handled = true ;
2019-07-19 23:37:45 +02:00
2018-04-05 19:59:35 +02:00
switch ( k - > get_keycode ( ) ) {
2021-08-13 23:31:57 +02:00
case Key : : ENTER : {
2019-07-19 23:37:45 +02:00
_open_selected_projects_ask ( ) ;
} break ;
2021-08-13 23:31:57 +02:00
case Key : : HOME : {
2019-07-19 23:37:45 +02:00
if ( _project_list - > get_project_count ( ) > 0 ) {
_project_list - > select_project ( 0 ) ;
_update_project_buttons ( ) ;
}
} break ;
2021-08-13 23:31:57 +02:00
case Key : : END : {
2019-07-19 23:37:45 +02:00
if ( _project_list - > get_project_count ( ) > 0 ) {
_project_list - > select_project ( _project_list - > get_project_count ( ) - 1 ) ;
_update_project_buttons ( ) ;
}
} break ;
2021-08-13 23:31:57 +02:00
case Key : : UP : {
2021-04-24 22:33:50 +02:00
if ( k - > is_shift_pressed ( ) ) {
2019-07-19 23:37:45 +02:00
break ;
2020-05-14 16:41:43 +02:00
}
2019-07-19 23:37:45 +02:00
int index = _project_list - > get_single_selected_index ( ) ;
2019-08-27 19:15:37 +02:00
if ( index > 0 ) {
2019-07-19 23:37:45 +02:00
_project_list - > select_project ( index - 1 ) ;
_project_list - > ensure_project_visible ( index - 1 ) ;
_update_project_buttons ( ) ;
}
break ;
2014-02-10 02:10:30 +01:00
}
2021-08-13 23:31:57 +02:00
case Key : : DOWN : {
2021-04-24 22:33:50 +02:00
if ( k - > is_shift_pressed ( ) ) {
2019-07-19 23:37:45 +02:00
break ;
2020-05-14 16:41:43 +02:00
}
2019-07-19 23:37:45 +02:00
int index = _project_list - > get_single_selected_index ( ) ;
if ( index + 1 < _project_list - > get_project_count ( ) ) {
_project_list - > select_project ( index + 1 ) ;
_project_list - > ensure_project_visible ( index + 1 ) ;
_update_project_buttons ( ) ;
}
} break ;
2021-08-13 23:31:57 +02:00
case Key : : F : {
2022-09-02 11:37:48 +02:00
if ( k - > is_command_or_control_pressed ( ) ) {
2020-08-27 15:43:49 +02:00
this - > search_box - > grab_focus ( ) ;
2020-05-14 16:41:43 +02:00
} else {
2018-04-05 19:59:35 +02:00
keycode_handled = false ;
2020-05-14 16:41:43 +02:00
}
2019-07-19 23:37:45 +02:00
} break ;
default : {
2018-04-05 19:59:35 +02:00
keycode_handled = false ;
2019-07-19 23:37:45 +02:00
} break ;
2014-02-10 02:10:30 +01:00
}
2018-04-05 19:59:35 +02:00
if ( keycode_handled ) {
2019-07-19 23:37:45 +02:00
accept_event ( ) ;
2014-02-10 02:10:30 +01:00
}
2019-07-19 23:37:45 +02:00
}
}
2014-02-10 02:10:30 +01:00
2018-12-21 12:20:48 +01:00
void ProjectManager : : _on_projects_updated ( ) {
2019-07-19 23:37:45 +02:00
Vector < ProjectList : : Item > selected_projects = _project_list - > get_selected_projects ( ) ;
int index = 0 ;
for ( int i = 0 ; i < selected_projects . size ( ) ; + + i ) {
index = _project_list - > refresh_project ( selected_projects [ i ] . path ) ;
}
if ( index ! = - 1 ) {
_project_list - > ensure_project_visible ( index ) ;
}
2019-10-24 12:25:21 +02:00
_project_list - > update_dock_menu ( ) ;
2017-09-07 02:04:41 +02:00
}
2016-11-07 19:39:05 +01:00
void ProjectManager : : _on_project_created ( const String & dir ) {
2022-02-24 02:19:27 +01:00
_project_list - > add_project ( dir , false ) ;
_project_list - > save_config ( ) ;
2020-08-27 15:43:49 +02:00
search_box - > clear ( ) ;
2019-07-19 23:37:45 +02:00
int i = _project_list - > refresh_project ( dir ) ;
_project_list - > select_project ( i ) ;
_project_list - > ensure_project_visible ( i ) ;
2018-12-21 12:20:48 +01:00
_open_selected_projects_ask ( ) ;
2019-10-24 12:25:21 +02:00
_project_list - > update_dock_menu ( ) ;
2016-11-07 19:39:05 +01:00
}
2018-12-21 12:20:48 +01:00
void ProjectManager : : _confirm_update_settings ( ) {
_open_selected_projects ( ) ;
}
2014-02-10 02:10:30 +01:00
2018-12-21 12:20:48 +01:00
void ProjectManager : : _open_selected_projects ( ) {
2021-02-07 01:47:35 +01:00
// Show loading text to tell the user that the project manager is busy loading.
2022-08-28 20:27:45 +02:00
// This is especially important for the Web project manager.
2022-05-20 03:08:01 +02:00
loading_label - > show ( ) ;
2021-02-07 01:47:35 +01:00
2022-05-19 17:00:06 +02:00
const HashSet < String > & selected_list = _project_list - > get_selected_project_keys ( ) ;
2019-07-19 23:37:45 +02:00
2022-02-24 02:19:27 +01:00
for ( const String & path : selected_list ) {
2022-08-30 02:34:01 +02:00
String conf = path . path_join ( " project.godot " ) ;
2019-07-19 23:37:45 +02:00
2017-09-11 07:01:20 +02:00
if ( ! FileAccess : : exists ( conf ) ) {
2018-12-21 12:20:48 +01:00
dialog_error - > set_text ( vformat ( TTR ( " Can't open project at '%s'. " ) , path ) ) ;
2020-03-06 18:00:16 +01:00
dialog_error - > popup_centered ( ) ;
2017-09-11 07:01:20 +02:00
return ;
}
2022-02-24 02:19:27 +01:00
print_line ( " Editing project: " + path ) ;
2014-02-10 02:10:30 +01:00
2014-05-11 03:45:52 +02:00
List < String > args ;
2014-02-10 02:10:30 +01:00
2022-08-13 21:52:03 +02:00
for ( const String & a : Main : : get_forwardable_cli_arguments ( Main : : CLI_SCOPE_TOOL ) ) {
args . push_back ( a ) ;
}
2017-06-21 11:09:30 +02:00
args . push_back ( " --path " ) ;
2014-05-11 03:45:52 +02:00
args . push_back ( path ) ;
2014-02-10 02:10:30 +01:00
2017-06-21 11:09:30 +02:00
args . push_back ( " --editor " ) ;
2014-02-10 02:10:30 +01:00
2021-11-01 10:12:52 +01:00
Error err = OS : : get_singleton ( ) - > create_instance ( args ) ;
2014-05-11 03:45:52 +02:00
ERR_FAIL_COND ( err ) ;
2014-02-10 02:10:30 +01:00
}
2022-03-04 19:18:40 +01:00
_project_list - > project_opening_initiated = true ;
2019-06-09 22:33:50 +02:00
_dim_window ( ) ;
2014-11-06 01:20:42 +01:00
get_tree ( ) - > quit ( ) ;
2014-05-11 03:45:52 +02:00
}
2014-02-10 02:10:30 +01:00
2018-12-21 12:20:48 +01:00
void ProjectManager : : _open_selected_projects_ask ( ) {
2022-05-19 17:00:06 +02:00
const HashSet < String > & selected_list = _project_list - > get_selected_project_keys ( ) ;
2019-07-19 23:37:45 +02:00
2014-05-11 03:45:52 +02:00
if ( selected_list . size ( ) < 1 ) {
return ;
}
2014-02-10 02:10:30 +01:00
2023-04-10 17:04:47 +02:00
const Size2i popup_min_size = Size2i ( 600.0 * EDSCALE , 0 ) ;
2022-09-15 00:45:59 +02:00
2014-05-11 03:45:52 +02:00
if ( selected_list . size ( ) > 1 ) {
2022-09-15 00:45:59 +02:00
multi_open_ask - > set_text ( vformat ( TTR ( " You requested to open %d projects in parallel. Do you confirm? \n Note that usual checks for engine version compatibility will be bypassed. " ) , selected_list . size ( ) ) ) ;
2023-02-18 20:42:46 +01:00
multi_open_ask - > popup_centered ( popup_min_size ) ;
2018-12-21 12:20:48 +01:00
return ;
}
2019-07-19 23:37:45 +02:00
ProjectList : : Item project = _project_list - > get_selected_projects ( ) [ 0 ] ;
2019-08-21 22:35:59 +02:00
if ( project . missing ) {
return ;
}
2018-12-21 12:20:48 +01:00
2022-09-15 00:45:59 +02:00
// Update the project settings or don't open.
2021-11-24 17:12:56 +01:00
const int config_version = project . version ;
PackedStringArray unsupported_features = project . unsupported_features ;
Label * ask_update_label = ask_update_settings - > get_label ( ) ;
2021-11-25 03:58:47 +01:00
ask_update_label - > set_horizontal_alignment ( HORIZONTAL_ALIGNMENT_LEFT ) ; // Reset in case of previous center align.
2022-08-26 15:22:30 +02:00
full_convert_button - > hide ( ) ;
ask_update_settings - > get_ok_button ( ) - > set_text ( " OK " ) ;
2019-02-02 02:04:50 +01:00
2022-09-15 00:45:59 +02:00
// Check if the config_version property was empty or 0.
2019-02-02 02:04:50 +01:00
if ( config_version = = 0 ) {
2022-09-15 00:45:59 +02:00
ask_update_settings - > set_text ( vformat ( TTR ( " The selected project \" %s \" does not specify its supported Godot version in its configuration file ( \" project.godot \" ). \n \n Project path: %s \n \n If you proceed with opening it, it will be converted to Godot's current configuration file format. \n \n Warning: You won't be able to open the project with previous versions of the engine anymore. " ) , project . project_name , project . path ) ) ;
2023-02-18 20:42:46 +01:00
ask_update_settings - > popup_centered ( popup_min_size ) ;
2019-02-02 02:04:50 +01:00
return ;
}
2022-09-15 00:45:59 +02:00
// Check if we need to convert project settings from an earlier engine version.
2018-12-21 12:20:48 +01:00
if ( config_version < ProjectSettings : : CONFIG_VERSION ) {
2022-08-26 15:22:30 +02:00
if ( config_version = = GODOT4_CONFIG_VERSION - 1 & & ProjectSettings : : CONFIG_VERSION = = GODOT4_CONFIG_VERSION ) { // Conversion from Godot 3 to 4.
full_convert_button - > show ( ) ;
2022-09-15 00:45:59 +02:00
ask_update_settings - > set_text ( vformat ( TTR ( " The selected project \" %s \" was generated by Godot 3.x, and needs to be converted for Godot 4.x. \n \n Project path: %s \n \n You have three options: \n - Convert only the configuration file ( \" project.godot \" ). Use this to open the project without attempting to convert its scenes, resources and scripts. \n - Convert the entire project including its scenes, resources and scripts (recommended if you are upgrading). \n - Do nothing and go back. \n \n Warning: If you select a conversion option, you won't be able to open the project with previous versions of the engine anymore. " ) , project . project_name , project . path ) ) ;
ask_update_settings - > get_ok_button ( ) - > set_text ( TTR ( " Convert project.godot Only " ) ) ;
} else {
ask_update_settings - > set_text ( vformat ( TTR ( " The selected project \" %s \" was generated by an older engine version, and needs to be converted for this version. \n \n Project path: %s \n \n Do you want to convert it? \n \n Warning: You won't be able to open the project with previous versions of the engine anymore. " ) , project . project_name , project . path ) ) ;
ask_update_settings - > get_ok_button ( ) - > set_text ( TTR ( " Convert project.godot " ) ) ;
2022-08-26 15:22:30 +02:00
}
2023-02-18 20:42:46 +01:00
ask_update_settings - > popup_centered ( popup_min_size ) ;
2022-09-15 00:45:59 +02:00
ask_update_settings - > get_cancel_button ( ) - > grab_focus ( ) ; // To prevent accidents.
2018-12-21 12:20:48 +01:00
return ;
}
2022-09-15 00:45:59 +02:00
// Check if the file was generated by a newer, incompatible engine version.
2018-12-21 12:20:48 +01:00
if ( config_version > ProjectSettings : : CONFIG_VERSION ) {
2022-09-15 00:45:59 +02:00
dialog_error - > set_text ( vformat ( TTR ( " Can't open project \" %s \" at the following path: \n \n %s \n \n The project settings were created by a newer engine version, whose settings are not compatible with this version. " ) , project . project_name , project . path ) ) ;
2023-02-18 20:42:46 +01:00
dialog_error - > popup_centered ( popup_min_size ) ;
2018-12-21 12:20:48 +01:00
return ;
2014-05-11 03:45:52 +02:00
}
2021-11-24 17:12:56 +01:00
// Check if the project is using features not supported by this build of Godot.
if ( ! unsupported_features . is_empty ( ) ) {
String warning_message = " " ;
for ( int i = 0 ; i < unsupported_features . size ( ) ; i + + ) {
String feature = unsupported_features [ i ] ;
if ( feature = = " Double Precision " ) {
warning_message + = TTR ( " Warning: This project uses double precision floats, but this version of \n Godot uses single precision floats. Opening this project may cause data loss. \n \n " ) ;
unsupported_features . remove_at ( i ) ;
i - - ;
} else if ( feature = = " C# " ) {
warning_message + = TTR ( " Warning: This project uses C#, but this build of Godot does not have \n the Mono module. If you proceed you will not be able to use any C# scripts. \n \n " ) ;
unsupported_features . remove_at ( i ) ;
i - - ;
2023-07-07 00:37:24 +02:00
} else if ( _project_feature_looks_like_version ( feature ) ) {
2023-07-10 18:52:48 +02:00
warning_message + = vformat ( TTR ( " Warning: This project was last edited in Godot %s. Opening will change it to Godot %s. \n \n " ) , Variant ( feature ) , Variant ( VERSION_BRANCH ) ) ;
2021-11-24 17:12:56 +01:00
unsupported_features . remove_at ( i ) ;
i - - ;
}
}
if ( ! unsupported_features . is_empty ( ) ) {
2022-05-10 15:25:03 +02:00
String unsupported_features_str = String ( " , " ) . join ( unsupported_features ) ;
2021-11-24 17:12:56 +01:00
warning_message + = vformat ( TTR ( " Warning: This project uses the following features not supported by this build of Godot: \n \n %s \n \n " ) , unsupported_features_str ) ;
}
warning_message + = TTR ( " Open anyway? Project will be modified. " ) ;
2021-11-25 03:58:47 +01:00
ask_update_label - > set_horizontal_alignment ( HORIZONTAL_ALIGNMENT_CENTER ) ;
2021-11-24 17:12:56 +01:00
ask_update_settings - > set_text ( warning_message ) ;
2023-02-18 20:42:46 +01:00
ask_update_settings - > popup_centered ( popup_min_size ) ;
2021-11-24 17:12:56 +01:00
return ;
}
2018-12-21 12:20:48 +01:00
2022-09-15 00:45:59 +02:00
// Open if the project is up-to-date.
2018-12-21 12:20:48 +01:00
_open_selected_projects ( ) ;
2014-02-10 02:10:30 +01:00
}
2022-09-15 00:45:59 +02:00
void ProjectManager : : _full_convert_button_pressed ( ) {
ask_update_settings - > hide ( ) ;
2023-04-10 17:04:47 +02:00
ask_full_convert_dialog - > popup_centered ( Size2i ( 600.0 * EDSCALE , 0 ) ) ;
2022-09-15 00:45:59 +02:00
ask_full_convert_dialog - > get_cancel_button ( ) - > grab_focus ( ) ;
}
2022-08-26 15:22:30 +02:00
void ProjectManager : : _perform_full_project_conversion ( ) {
Vector < ProjectList : : Item > selected_list = _project_list - > get_selected_projects ( ) ;
if ( selected_list . is_empty ( ) ) {
return ;
}
const String & path = selected_list [ 0 ] . path ;
print_line ( " Converting project: " + path ) ;
List < String > args ;
args . push_back ( " --path " ) ;
args . push_back ( path ) ;
args . push_back ( " --convert-3to4 " ) ;
2023-06-28 15:07:37 +02:00
args . push_back ( " --rendering-driver " ) ;
args . push_back ( Main : : get_rendering_driver_name ( ) ) ;
2022-08-26 15:22:30 +02:00
Error err = OS : : get_singleton ( ) - > create_instance ( args ) ;
ERR_FAIL_COND ( err ) ;
2023-05-16 17:26:58 +02:00
_project_list - > set_project_version ( path , GODOT4_CONFIG_VERSION ) ;
2022-08-26 15:22:30 +02:00
}
2014-05-11 03:45:52 +02:00
void ProjectManager : : _run_project_confirm ( ) {
2019-07-19 23:37:45 +02:00
Vector < ProjectList : : Item > selected_list = _project_list - > get_selected_projects ( ) ;
for ( int i = 0 ; i < selected_list . size ( ) ; + + i ) {
const String & selected_main = selected_list [ i ] . main_scene ;
2021-12-09 10:42:46 +01:00
if ( selected_main . is_empty ( ) ) {
2019-03-25 01:54:29 +01:00
run_error_diag - > set_text ( TTR ( " Can't run project: no main scene defined. \n Please edit the project and set the main scene in the Project Settings under the \" Application \" category. " ) ) ;
2017-08-07 21:41:04 +02:00
run_error_diag - > popup_centered ( ) ;
2020-11-18 21:24:27 +01:00
continue ;
2017-08-07 21:41:04 +02:00
}
2022-02-24 02:19:27 +01:00
const String & path = selected_list [ i ] . path ;
2017-08-07 21:41:04 +02:00
2021-09-10 17:32:29 +02:00
// `.substr(6)` on `ProjectSettings::get_singleton()->get_imported_files_path()` strips away the leading "res://".
2022-08-30 02:34:01 +02:00
if ( ! DirAccess : : exists ( path . path_join ( ProjectSettings : : get_singleton ( ) - > get_imported_files_path ( ) . substr ( 6 ) ) ) ) {
2017-08-07 21:41:04 +02:00
run_error_diag - > set_text ( TTR ( " Can't run project: Assets need to be imported. \n Please edit the project to trigger the initial import. " ) ) ;
run_error_diag - > popup_centered ( ) ;
2020-11-18 21:24:27 +01:00
continue ;
2017-08-07 21:41:04 +02:00
}
2022-02-24 02:19:27 +01:00
print_line ( " Running project: " + path ) ;
2014-02-10 02:10:30 +01:00
2014-05-11 03:45:52 +02:00
List < String > args ;
2014-02-10 02:10:30 +01:00
2022-08-13 21:52:03 +02:00
for ( const String & a : Main : : get_forwardable_cli_arguments ( Main : : CLI_SCOPE_PROJECT ) ) {
args . push_back ( a ) ;
}
2017-06-21 11:09:30 +02:00
args . push_back ( " --path " ) ;
2014-05-11 03:45:52 +02:00
args . push_back ( path ) ;
2014-02-10 02:10:30 +01:00
2021-11-01 10:12:52 +01:00
Error err = OS : : get_singleton ( ) - > create_instance ( args ) ;
2014-05-11 03:45:52 +02:00
ERR_FAIL_COND ( err ) ;
}
}
2014-02-10 02:10:30 +01:00
2014-05-11 03:45:52 +02:00
void ProjectManager : : _run_project ( ) {
2022-05-19 17:00:06 +02:00
const HashSet < String > & selected_list = _project_list - > get_selected_project_keys ( ) ;
2019-07-19 23:37:45 +02:00
2014-05-11 03:45:52 +02:00
if ( selected_list . size ( ) < 1 ) {
return ;
}
if ( selected_list . size ( ) > 1 ) {
2019-03-25 01:54:29 +01:00
multi_run_ask - > set_text ( vformat ( TTR ( " Are you sure to run %d projects at once? " ) , selected_list . size ( ) ) ) ;
2020-03-06 18:00:16 +01:00
multi_run_ask - > popup_centered ( ) ;
2014-05-11 03:45:52 +02:00
} else {
_run_project_confirm ( ) ;
}
2014-02-10 02:10:30 +01:00
}
void ProjectManager : : _scan_projects ( ) {
2020-07-11 18:45:19 +02:00
scan_dir - > popup_file_dialog ( ) ;
2014-02-10 02:10:30 +01:00
}
void ProjectManager : : _new_project ( ) {
2017-08-31 03:08:17 +02:00
npdialog - > set_mode ( ProjectDialog : : MODE_NEW ) ;
2014-02-10 02:10:30 +01:00
npdialog - > show_dialog ( ) ;
}
void ProjectManager : : _import_project ( ) {
2017-08-31 03:08:17 +02:00
npdialog - > set_mode ( ProjectDialog : : MODE_IMPORT ) ;
2023-07-30 12:09:54 +02:00
npdialog - > ask_for_path_and_show ( ) ;
2014-02-10 02:10:30 +01:00
}
2017-09-07 02:04:41 +02:00
void ProjectManager : : _rename_project ( ) {
2022-05-19 17:00:06 +02:00
const HashSet < String > & selected_list = _project_list - > get_selected_project_keys ( ) ;
2019-07-19 23:37:45 +02:00
2017-09-07 02:04:41 +02:00
if ( selected_list . size ( ) = = 0 ) {
return ;
}
2022-05-19 01:43:40 +02:00
for ( const String & E : selected_list ) {
2022-02-24 02:19:27 +01:00
npdialog - > set_project_path ( E ) ;
2017-08-31 03:08:17 +02:00
npdialog - > set_mode ( ProjectDialog : : MODE_RENAME ) ;
2017-09-07 02:04:41 +02:00
npdialog - > show_dialog ( ) ;
}
}
2023-03-17 23:30:21 +01:00
void ProjectManager : : _manage_project_tags ( ) {
for ( int i = 0 ; i < project_tags - > get_child_count ( ) ; i + + ) {
project_tags - > get_child ( i ) - > queue_free ( ) ;
}
const ProjectList : : Item item = _project_list - > get_selected_projects ( ) [ 0 ] ;
current_project_tags = item . tags ;
for ( const String & tag : current_project_tags ) {
ProjectTag * tag_control = memnew ( ProjectTag ( tag , true ) ) ;
project_tags - > add_child ( tag_control ) ;
tag_control - > connect_button_to ( callable_mp ( this , & ProjectManager : : _delete_project_tag ) . bind ( tag ) ) ;
}
tag_edit_error - > hide ( ) ;
tag_manage_dialog - > popup_centered ( Vector2i ( 500 , 0 ) * EDSCALE ) ;
}
void ProjectManager : : _add_project_tag ( const String & p_tag ) {
if ( current_project_tags . has ( p_tag ) ) {
return ;
}
current_project_tags . append ( p_tag ) ;
ProjectTag * tag_control = memnew ( ProjectTag ( p_tag , true ) ) ;
project_tags - > add_child ( tag_control ) ;
tag_control - > connect_button_to ( callable_mp ( this , & ProjectManager : : _delete_project_tag ) . bind ( p_tag ) ) ;
}
void ProjectManager : : _delete_project_tag ( const String & p_tag ) {
current_project_tags . erase ( p_tag ) ;
for ( int i = 0 ; i < project_tags - > get_child_count ( ) ; i + + ) {
ProjectTag * tag_control = Object : : cast_to < ProjectTag > ( project_tags - > get_child ( i ) ) ;
if ( tag_control & & tag_control - > get_tag ( ) = = p_tag ) {
memdelete ( tag_control ) ;
break ;
}
}
}
void ProjectManager : : _apply_project_tags ( ) {
PackedStringArray tags ;
for ( int i = 0 ; i < project_tags - > get_child_count ( ) ; i + + ) {
ProjectTag * tag_control = Object : : cast_to < ProjectTag > ( project_tags - > get_child ( i ) ) ;
if ( tag_control ) {
tags . append ( tag_control - > get_tag ( ) ) ;
}
}
ConfigFile cfg ;
2023-06-09 12:34:07 +02:00
const String project_godot = _project_list - > get_selected_projects ( ) [ 0 ] . path . path_join ( " project.godot " ) ;
2023-03-17 23:30:21 +01:00
Error err = cfg . load ( project_godot ) ;
if ( err ! = OK ) {
tag_edit_error - > set_text ( vformat ( TTR ( " Couldn't load project at '%s' (error %d). It may be missing or corrupted. " ) , project_godot , err ) ) ;
tag_edit_error - > show ( ) ;
callable_mp ( ( Window * ) tag_manage_dialog , & Window : : show ) . call_deferred ( ) ; // Make sure the dialog does not disappear.
return ;
} else {
2023-06-28 01:22:33 +02:00
tags . sort ( ) ;
2023-03-17 23:30:21 +01:00
cfg . set_value ( " application " , " config/tags " , tags ) ;
err = cfg . save ( project_godot ) ;
if ( err ! = OK ) {
tag_edit_error - > set_text ( vformat ( TTR ( " Couldn't save project at '%s' (error %d). " ) , project_godot , err ) ) ;
tag_edit_error - > show ( ) ;
callable_mp ( ( Window * ) tag_manage_dialog , & Window : : show ) . call_deferred ( ) ;
return ;
}
}
_on_projects_updated ( ) ;
}
void ProjectManager : : _set_new_tag_name ( const String p_name ) {
create_tag_dialog - > get_ok_button ( ) - > set_disabled ( true ) ;
if ( p_name . is_empty ( ) ) {
tag_error - > set_text ( TTR ( " Tag name can't be empty. " ) ) ;
return ;
}
if ( p_name . contains ( " " ) ) {
tag_error - > set_text ( TTR ( " Tag name can't contain spaces. " ) ) ;
return ;
}
for ( const String & c : forbidden_tag_characters ) {
if ( p_name . contains ( c ) ) {
tag_error - > set_text ( vformat ( TTR ( " These characters are not allowed in tags: %s. " ) , String ( " " ) . join ( forbidden_tag_characters ) ) ) ;
return ;
}
}
if ( p_name . to_lower ( ) ! = p_name ) {
tag_error - > set_text ( TTR ( " Tag name must be lowercase. " ) ) ;
return ;
}
tag_error - > set_text ( " " ) ;
create_tag_dialog - > get_ok_button ( ) - > set_disabled ( false ) ;
}
void ProjectManager : : _create_new_tag ( ) {
if ( ! tag_error - > get_text ( ) . is_empty ( ) ) {
return ;
}
create_tag_dialog - > hide ( ) ; // When using text_submitted, need to hide manually.
add_new_tag ( new_tag_name - > get_text ( ) ) ;
_add_project_tag ( new_tag_name - > get_text ( ) ) ;
}
2014-02-10 02:10:30 +01:00
void ProjectManager : : _erase_project_confirm ( ) {
2023-03-15 23:48:52 +01:00
_project_list - > erase_selected_projects ( false ) ;
2019-08-15 03:30:42 +02:00
_update_project_buttons ( ) ;
2014-02-10 02:10:30 +01:00
}
2019-02-11 17:44:23 +01:00
void ProjectManager : : _erase_missing_projects_confirm ( ) {
2019-07-19 23:37:45 +02:00
_project_list - > erase_missing_projects ( ) ;
2019-08-15 03:30:42 +02:00
_update_project_buttons ( ) ;
2019-02-11 17:44:23 +01:00
}
2014-02-10 02:10:30 +01:00
void ProjectManager : : _erase_project ( ) {
2022-05-19 17:00:06 +02:00
const HashSet < String > & selected_list = _project_list - > get_selected_project_keys ( ) ;
2019-07-19 23:37:45 +02:00
2020-05-14 16:41:43 +02:00
if ( selected_list . size ( ) = = 0 ) {
2014-02-10 02:10:30 +01:00
return ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
2019-03-25 01:54:29 +01:00
String confirm_message ;
if ( selected_list . size ( ) > = 2 ) {
2021-03-20 19:31:49 +01:00
confirm_message = vformat ( TTR ( " Remove %d projects from the list? " ) , selected_list . size ( ) ) ;
2019-03-25 01:54:29 +01:00
} else {
2021-03-20 19:31:49 +01:00
confirm_message = TTR ( " Remove this project from the list? " ) ;
2019-03-25 01:54:29 +01:00
}
2021-03-20 19:31:49 +01:00
erase_ask_label - > set_text ( confirm_message ) ;
2023-03-15 23:48:52 +01:00
//delete_project_contents->set_pressed(false);
2020-03-06 18:00:16 +01:00
erase_ask - > popup_centered ( ) ;
2014-02-10 02:10:30 +01:00
}
2019-02-11 17:44:23 +01:00
void ProjectManager : : _erase_missing_projects ( ) {
2019-08-21 22:35:59 +02:00
erase_missing_ask - > set_text ( TTR ( " Remove all missing projects from the list? \n The project folders' contents won't be modified. " ) ) ;
2020-03-06 18:00:16 +01:00
erase_missing_ask - > popup_centered ( ) ;
2019-02-11 17:44:23 +01:00
}
2021-04-08 01:05:47 +02:00
void ProjectManager : : _show_about ( ) {
about - > popup_centered ( Size2 ( 780 , 500 ) * EDSCALE ) ;
}
2017-10-26 00:02:32 +02:00
void ProjectManager : : _language_selected ( int p_id ) {
String lang = language_btn - > get_item_metadata ( p_id ) ;
EditorSettings : : get_singleton ( ) - > set ( " interface/editor/editor_language " , lang ) ;
2019-03-25 01:54:29 +01:00
language_restart_ask - > set_text ( TTR ( " Language changed. \n The interface will update after restarting the editor or project manager. " ) ) ;
2017-10-26 00:02:32 +02:00
language_restart_ask - > popup_centered ( ) ;
}
void ProjectManager : : _restart_confirm ( ) {
List < String > args = OS : : get_singleton ( ) - > get_cmdline_args ( ) ;
2021-11-01 10:12:52 +01:00
Error err = OS : : get_singleton ( ) - > create_instance ( args ) ;
2017-10-26 00:02:32 +02:00
ERR_FAIL_COND ( err ) ;
2019-06-09 22:33:50 +02:00
_dim_window ( ) ;
2017-10-26 00:02:32 +02:00
get_tree ( ) - > quit ( ) ;
}
2016-07-12 02:34:02 +02:00
void ProjectManager : : _install_project ( const String & p_zip_path , const String & p_title ) {
2017-08-31 03:08:17 +02:00
npdialog - > set_mode ( ProjectDialog : : MODE_INSTALL ) ;
2016-07-12 02:34:02 +02:00
npdialog - > set_zip_path ( p_zip_path ) ;
npdialog - > set_zip_title ( p_title ) ;
npdialog - > show_dialog ( ) ;
}
2022-04-10 21:25:24 +02:00
void ProjectManager : : _files_dropped ( PackedStringArray p_files ) {
2023-12-20 13:09:36 +01:00
// TODO: Support installing multiple ZIPs at the same time?
2021-02-03 17:25:08 +01:00
if ( p_files . size ( ) = = 1 & & p_files [ 0 ] . ends_with ( " .zip " ) ) {
2023-12-20 13:09:36 +01:00
const String & file = p_files [ 0 ] ;
_install_project ( file , file . get_file ( ) . get_basename ( ) . capitalize ( ) ) ;
2021-02-03 17:25:08 +01:00
return ;
}
2023-12-20 13:09:36 +01:00
2022-05-19 17:00:06 +02:00
HashSet < String > folders_set ;
2022-03-23 10:08:58 +01:00
Ref < DirAccess > da = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
2016-07-29 17:06:37 +02:00
for ( int i = 0 ; i < p_files . size ( ) ; i + + ) {
2023-12-20 13:09:36 +01:00
const String & file = p_files [ i ] ;
2016-08-01 00:59:31 +02:00
folders_set . insert ( da - > dir_exists ( file ) ? file : file . get_base_dir ( ) ) ;
}
2023-12-20 13:09:36 +01:00
ERR_FAIL_COND ( folders_set . size ( ) = = 0 ) ; // This can't really happen, we consume every dropped file path above.
2016-08-01 00:59:31 +02:00
2023-12-20 13:09:36 +01:00
PackedStringArray folders ;
for ( const String & E : folders_set ) {
folders . push_back ( E ) ;
2016-07-31 00:10:13 +02:00
}
2023-12-20 13:09:36 +01:00
_project_list - > find_projects_multiple ( folders ) ;
2016-07-29 17:06:37 +02:00
}
2020-08-27 15:43:49 +02:00
void ProjectManager : : _on_order_option_changed ( int p_idx ) {
2020-08-29 15:39:34 +02:00
if ( is_inside_tree ( ) ) {
_project_list - > set_order_option ( p_idx ) ;
2020-08-27 15:43:49 +02:00
}
2019-07-19 23:37:45 +02:00
}
2021-04-04 22:47:44 +02:00
void ProjectManager : : _on_tab_changed ( int p_tab ) {
2022-09-06 07:13:03 +02:00
# ifndef ANDROID_ENABLED
2021-04-04 22:47:44 +02:00
if ( p_tab = = 0 ) { // Projects
// Automatically grab focus when the user moves from the Templates tab
// back to the Projects tab.
search_box - > grab_focus ( ) ;
}
// The Templates tab's search field is focused on display in the asset
// library editor plugin code.
2022-09-06 07:13:03 +02:00
# endif
2021-04-04 22:47:44 +02:00
}
2020-08-27 15:43:49 +02:00
void ProjectManager : : _on_search_term_changed ( const String & p_term ) {
_project_list - > set_search_term ( p_term ) ;
2019-07-19 23:37:45 +02:00
_project_list - > sort_projects ( ) ;
2020-01-17 22:07:11 +01:00
// Select the first visible project in the list.
// This makes it possible to open a project without ever touching the mouse,
// as the search field is automatically focused on startup.
_project_list - > select_first_visible_project ( ) ;
_update_project_buttons ( ) ;
2019-07-19 23:37:45 +02:00
}
2023-10-12 21:12:22 +02:00
void ProjectManager : : _on_search_term_submitted ( const String & p_text ) {
if ( tabs - > get_current_tab ( ) ! = 0 ) {
return ;
}
_open_selected_projects_ask ( ) ;
}
2017-11-26 19:31:08 +01:00
void ProjectManager : : _open_asset_library ( ) {
asset_library - > disable_community_support ( ) ;
tabs - > set_current_tab ( 1 ) ;
}
2019-12-24 01:20:54 +01:00
void ProjectManager : : _version_button_pressed ( ) {
DisplayServer : : get_singleton ( ) - > clipboard_set ( version_btn - > get_text ( ) ) ;
}
2023-03-17 23:30:21 +01:00
LineEdit * ProjectManager : : get_search_box ( ) {
return search_box ;
}
void ProjectManager : : add_new_tag ( const String & p_tag ) {
if ( ! tag_set . has ( p_tag ) ) {
tag_set . insert ( p_tag ) ;
ProjectTag * tag_control = memnew ( ProjectTag ( p_tag ) ) ;
all_tags - > add_child ( tag_control ) ;
all_tags - > move_child ( tag_control , - 2 ) ;
tag_control - > connect_button_to ( callable_mp ( this , & ProjectManager : : _add_project_tag ) . bind ( p_tag ) ) ;
}
}
void ProjectList : : add_search_tag ( const String & p_tag ) {
const String tag_string = " tag: " + p_tag ;
int exists = _search_term . find ( tag_string ) ;
if ( exists > - 1 ) {
_search_term = _search_term . erase ( exists , tag_string . length ( ) + 1 ) ;
} else if ( _search_term . is_empty ( ) | | _search_term . ends_with ( " " ) ) {
_search_term + = tag_string ;
} else {
_search_term + = " " + tag_string ;
}
ProjectManager : : get_singleton ( ) - > get_search_box ( ) - > set_text ( _search_term ) ;
sort_projects ( ) ;
}
2014-02-10 02:10:30 +01:00
ProjectManager : : ProjectManager ( ) {
2022-02-12 20:55:11 +01:00
singleton = this ;
2014-02-10 02:10:30 +01:00
// load settings
2020-05-14 16:41:43 +02:00
if ( ! EditorSettings : : get_singleton ( ) ) {
2014-02-10 02:10:30 +01:00
EditorSettings : : create ( ) ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
2021-10-30 16:15:00 +02:00
// Turn off some servers we aren't going to be using in the Project Manager.
NavigationServer3D : : get_singleton ( ) - > set_active ( false ) ;
PhysicsServer3D : : get_singleton ( ) - > set_active ( false ) ;
PhysicsServer2D : : get_singleton ( ) - > set_active ( false ) ;
2016-07-04 00:13:45 +02:00
EditorSettings : : get_singleton ( ) - > set_optimize_save ( false ) ; //just write settings as they came
2016-06-05 23:43:45 +02:00
{
2022-10-18 16:43:37 +02:00
int display_scale = EDITOR_GET ( " interface/editor/display_scale " ) ;
2018-03-22 20:20:42 +01:00
switch ( display_scale ) {
2021-06-16 14:36:09 +02:00
case 0 :
2020-11-12 16:01:27 +01:00
// Try applying a suitable display scale automatically.
2023-05-17 17:22:26 +02:00
EditorScale : : set_scale ( EditorSettings : : get_singleton ( ) - > get_auto_display_scale ( ) ) ;
2021-06-16 14:36:09 +02:00
break ;
2020-05-10 13:00:47 +02:00
case 1 :
2023-05-17 17:22:26 +02:00
EditorScale : : set_scale ( 0.75 ) ;
2020-05-10 13:00:47 +02:00
break ;
case 2 :
2023-05-17 17:22:26 +02:00
EditorScale : : set_scale ( 1.0 ) ;
2020-05-10 13:00:47 +02:00
break ;
case 3 :
2023-05-17 17:22:26 +02:00
EditorScale : : set_scale ( 1.25 ) ;
2020-05-10 13:00:47 +02:00
break ;
case 4 :
2023-05-17 17:22:26 +02:00
EditorScale : : set_scale ( 1.5 ) ;
2020-05-10 13:00:47 +02:00
break ;
case 5 :
2023-05-17 17:22:26 +02:00
EditorScale : : set_scale ( 1.75 ) ;
2020-05-10 13:00:47 +02:00
break ;
case 6 :
2023-05-17 17:22:26 +02:00
EditorScale : : set_scale ( 2.0 ) ;
2020-05-10 13:00:47 +02:00
break ;
2020-11-12 16:01:27 +01:00
default :
2023-05-17 17:22:26 +02:00
EditorScale : : set_scale ( EDITOR_GET ( " interface/editor/custom_display_scale " ) ) ;
2020-11-12 16:01:27 +01:00
break ;
2016-06-05 23:43:45 +02:00
}
2022-02-03 01:21:52 +01:00
EditorFileDialog : : get_icon_func = & ProjectManager : : _file_dialog_get_icon ;
2023-02-17 22:37:38 +01:00
EditorFileDialog : : get_thumbnail_func = & ProjectManager : : _file_dialog_get_thumbnail ;
2016-06-05 23:43:45 +02:00
}
2020-09-08 13:56:08 +02:00
// TRANSLATORS: This refers to the application where users manage their Godot projects.
2022-03-18 08:01:08 +01:00
DisplayServer : : get_singleton ( ) - > window_set_title ( VERSION_NAME + String ( " - " ) + TTR ( " Project Manager " , " Application " ) ) ;
2020-08-27 15:43:49 +02:00
2022-10-18 16:43:37 +02:00
EditorFileDialog : : set_default_show_hidden_files ( EDITOR_GET ( " filesystem/file_dialog/show_hidden_files " ) ) ;
2023-04-25 02:57:22 +02:00
EditorFileDialog : : set_default_display_mode ( ( EditorFileDialog : : DisplayMode ) EDITOR_GET ( " filesystem/file_dialog/display_mode " ) . operator int ( ) ) ;
2014-02-10 02:10:30 +01:00
2022-03-21 10:55:18 +01:00
int swap_cancel_ok = EDITOR_GET ( " interface/editor/accept_dialog_cancel_ok_buttons " ) ;
if ( swap_cancel_ok ! = 0 ) { // 0 is auto, set in register_scene based on DisplayServer.
// Swap on means OK first.
AcceptDialog : : set_swap_cancel_ok ( swap_cancel_ok = = 2 ) ;
}
2023-11-17 07:54:07 +01:00
int pm_root_dir = EDITOR_GET ( " interface/editor/ui_layout_direction " ) ;
Control : : set_root_layout_direction ( pm_root_dir ) ;
Window : : set_root_layout_direction ( pm_root_dir ) ;
2023-03-10 15:22:33 +01:00
EditorColorMap : : create ( ) ;
2023-10-18 17:36:20 +02:00
EditorTheme : : initialize ( ) ;
2023-01-11 19:14:43 +01:00
Ref < Theme > theme = create_custom_theme ( ) ;
2023-08-13 02:33:39 +02:00
DisplayServer : : set_early_window_clear_color_override ( true , theme - > get_color ( SNAME ( " background " ) , EditorStringName ( Editor ) ) ) ;
2015-12-09 16:35:20 +01:00
2023-03-10 15:22:33 +01:00
set_theme ( theme ) ;
2022-03-19 01:02:57 +01:00
set_anchors_and_offsets_preset ( Control : : PRESET_FULL_RECT ) ;
2015-12-09 16:35:20 +01:00
2023-03-10 15:22:33 +01:00
background_panel = memnew ( Panel ) ;
add_child ( background_panel ) ;
background_panel - > set_anchors_and_offsets_preset ( Control : : PRESET_FULL_RECT ) ;
2014-02-10 02:10:30 +01:00
VBoxContainer * vb = memnew ( VBoxContainer ) ;
2023-03-10 15:22:33 +01:00
background_panel - > add_child ( vb ) ;
2022-03-19 01:02:57 +01:00
vb - > set_anchors_and_offsets_preset ( Control : : PRESET_FULL_RECT , Control : : PRESET_MODE_MINSIZE , 8 * EDSCALE ) ;
2014-02-10 02:10:30 +01:00
2017-10-26 00:02:32 +02:00
Control * center_box = memnew ( Control ) ;
2020-03-06 18:00:16 +01:00
center_box - > set_v_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
2017-10-26 00:02:32 +02:00
vb - > add_child ( center_box ) ;
2016-06-08 14:25:47 +02:00
tabs = memnew ( TabContainer ) ;
2022-03-19 01:02:57 +01:00
tabs - > set_anchors_and_offsets_preset ( Control : : PRESET_FULL_RECT ) ;
2023-12-20 13:09:36 +01:00
center_box - > add_child ( tabs ) ;
2021-04-04 22:47:44 +02:00
tabs - > connect ( " tab_changed " , callable_mp ( this , & ProjectManager : : _on_tab_changed ) ) ;
2014-02-10 02:10:30 +01:00
2023-07-25 18:58:14 +02:00
local_projects_vb = memnew ( VBoxContainer ) ;
local_projects_vb - > set_name ( TTR ( " Local Projects " ) ) ;
tabs - > add_child ( local_projects_vb ) ;
2016-06-08 14:25:47 +02:00
2020-08-27 15:43:49 +02:00
{
2023-07-25 18:58:14 +02:00
// A bar at top with buttons and options.
2020-08-27 15:43:49 +02:00
HBoxContainer * hb = memnew ( HBoxContainer ) ;
hb - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
2023-07-25 18:58:14 +02:00
local_projects_vb - > add_child ( hb ) ;
create_btn = memnew ( Button ) ;
create_btn - > set_text ( TTR ( " New " ) ) ;
create_btn - > set_shortcut ( ED_SHORTCUT ( " project_manager/new_project " , TTR ( " New Project " ) , KeyModifierMask : : CMD_OR_CTRL | Key : : N ) ) ;
create_btn - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _new_project ) ) ;
hb - > add_child ( create_btn ) ;
import_btn = memnew ( Button ) ;
import_btn - > set_text ( TTR ( " Import " ) ) ;
import_btn - > set_shortcut ( ED_SHORTCUT ( " project_manager/import_project " , TTR ( " Import Project " ) , KeyModifierMask : : CMD_OR_CTRL | Key : : I ) ) ;
import_btn - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _import_project ) ) ;
hb - > add_child ( import_btn ) ;
scan_btn = memnew ( Button ) ;
scan_btn - > set_text ( TTR ( " Scan " ) ) ;
scan_btn - > set_shortcut ( ED_SHORTCUT ( " project_manager/scan_projects " , TTR ( " Scan Projects " ) , KeyModifierMask : : CMD_OR_CTRL | Key : : S ) ) ;
scan_btn - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _scan_projects ) ) ;
hb - > add_child ( scan_btn ) ;
loading_label = memnew ( Label ( TTR ( " Loading, please wait... " ) ) ) ;
loading_label - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
hb - > add_child ( loading_label ) ;
// The loading label is shown later.
loading_label - > hide ( ) ;
2014-02-10 02:10:30 +01:00
2020-08-27 15:43:49 +02:00
search_box = memnew ( LineEdit ) ;
2022-05-27 06:02:48 +02:00
search_box - > set_placeholder ( TTR ( " Filter Projects " ) ) ;
2022-08-25 12:42:17 +02:00
search_box - > set_tooltip_text ( TTR ( " This field filters projects by name and last path component. \n To filter projects by name and full path, the query must contain at least one `/` character. " ) ) ;
2023-03-10 15:22:33 +01:00
search_box - > set_clear_button_enabled ( true ) ;
2020-08-27 15:43:49 +02:00
search_box - > connect ( " text_changed " , callable_mp ( this , & ProjectManager : : _on_search_term_changed ) ) ;
2023-10-12 21:12:22 +02:00
search_box - > connect ( " text_submitted " , callable_mp ( this , & ProjectManager : : _on_search_term_submitted ) ) ;
2020-08-27 15:43:49 +02:00
search_box - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
hb - > add_child ( search_box ) ;
2017-10-26 00:02:32 +02:00
2020-08-27 15:43:49 +02:00
Label * sort_label = memnew ( Label ) ;
sort_label - > set_text ( TTR ( " Sort: " ) ) ;
hb - > add_child ( sort_label ) ;
2017-10-26 00:02:32 +02:00
2020-08-27 15:43:49 +02:00
filter_option = memnew ( OptionButton ) ;
filter_option - > set_clip_text ( true ) ;
2022-05-20 03:08:01 +02:00
filter_option - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
2023-07-25 18:58:14 +02:00
filter_option - > set_stretch_ratio ( 0.3 ) ;
2020-08-27 15:43:49 +02:00
filter_option - > connect ( " item_selected " , callable_mp ( this , & ProjectManager : : _on_order_option_changed ) ) ;
hb - > add_child ( filter_option ) ;
2017-10-26 00:02:32 +02:00
2020-08-27 15:43:49 +02:00
Vector < String > sort_filter_titles ;
2021-11-07 22:50:29 +01:00
sort_filter_titles . push_back ( TTR ( " Last Edited " ) ) ;
2020-08-27 15:43:49 +02:00
sort_filter_titles . push_back ( TTR ( " Name " ) ) ;
sort_filter_titles . push_back ( TTR ( " Path " ) ) ;
2023-03-17 23:30:21 +01:00
sort_filter_titles . push_back ( TTR ( " Tags " ) ) ;
2017-10-26 00:02:32 +02:00
2020-08-27 15:43:49 +02:00
for ( int i = 0 ; i < sort_filter_titles . size ( ) ; i + + ) {
filter_option - > add_item ( sort_filter_titles [ i ] ) ;
}
2023-07-25 18:58:14 +02:00
}
{
// A container for the project list and for the side bar with buttons.
HBoxContainer * search_tree_hb = memnew ( HBoxContainer ) ;
local_projects_vb - > add_child ( search_tree_hb ) ;
search_tree_hb - > set_v_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
2014-02-10 02:10:30 +01:00
2023-03-10 15:22:33 +01:00
search_panel = memnew ( PanelContainer ) ;
2023-07-25 18:58:14 +02:00
search_panel - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
search_tree_hb - > add_child ( search_panel ) ;
2017-10-26 00:02:32 +02:00
2020-08-27 15:43:49 +02:00
_project_list = memnew ( ProjectList ) ;
2021-12-07 17:15:18 +01:00
_project_list - > set_horizontal_scroll_mode ( ScrollContainer : : SCROLL_MODE_DISABLED ) ;
2023-03-10 15:22:33 +01:00
search_panel - > add_child ( _project_list ) ;
2023-12-20 13:09:36 +01:00
_project_list - > connect ( ProjectList : : SIGNAL_LIST_CHANGED , callable_mp ( this , & ProjectManager : : _update_project_buttons ) ) ;
_project_list - > connect ( ProjectList : : SIGNAL_SELECTION_CHANGED , callable_mp ( this , & ProjectManager : : _update_project_buttons ) ) ;
_project_list - > connect ( ProjectList : : SIGNAL_PROJECT_ASK_OPEN , callable_mp ( this , & ProjectManager : : _open_selected_projects_ask ) ) ;
2019-02-11 17:44:23 +01:00
2023-07-25 18:58:14 +02:00
// The side bar with the edit, run, rename, etc. buttons.
2020-08-27 15:43:49 +02:00
VBoxContainer * tree_vb = memnew ( VBoxContainer ) ;
2020-03-23 08:38:21 +01:00
tree_vb - > set_custom_minimum_size ( Size2 ( 120 , 120 ) ) ;
2023-07-25 18:58:14 +02:00
search_tree_hb - > add_child ( tree_vb ) ;
2014-05-11 03:45:52 +02:00
2020-08-27 15:43:49 +02:00
tree_vb - > add_child ( memnew ( HSeparator ) ) ;
2018-12-21 12:20:48 +01:00
2020-08-27 15:43:49 +02:00
open_btn = memnew ( Button ) ;
open_btn - > set_text ( TTR ( " Edit " ) ) ;
2022-09-02 11:37:48 +02:00
open_btn - > set_shortcut ( ED_SHORTCUT ( " project_manager/edit_project " , TTR ( " Edit Project " ) , KeyModifierMask : : CMD_OR_CTRL | Key : : E ) ) ;
2020-08-27 15:43:49 +02:00
open_btn - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _open_selected_projects_ask ) ) ;
tree_vb - > add_child ( open_btn ) ;
2014-02-10 02:10:30 +01:00
2020-08-27 15:43:49 +02:00
run_btn = memnew ( Button ) ;
run_btn - > set_text ( TTR ( " Run " ) ) ;
2022-09-02 11:37:48 +02:00
run_btn - > set_shortcut ( ED_SHORTCUT ( " project_manager/run_project " , TTR ( " Run Project " ) , KeyModifierMask : : CMD_OR_CTRL | Key : : R ) ) ;
2020-08-27 15:43:49 +02:00
run_btn - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _run_project ) ) ;
tree_vb - > add_child ( run_btn ) ;
2014-02-10 02:10:30 +01:00
2020-08-27 15:43:49 +02:00
rename_btn = memnew ( Button ) ;
rename_btn - > set_text ( TTR ( " Rename " ) ) ;
2021-11-12 19:54:12 +01:00
// The F2 shortcut isn't overridden with Enter on macOS as Enter is already used to edit a project.
2021-08-13 23:31:57 +02:00
rename_btn - > set_shortcut ( ED_SHORTCUT ( " project_manager/rename_project " , TTR ( " Rename Project " ) , Key : : F2 ) ) ;
2020-08-27 15:43:49 +02:00
rename_btn - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _rename_project ) ) ;
tree_vb - > add_child ( rename_btn ) ;
2019-07-19 23:37:45 +02:00
2023-03-17 23:30:21 +01:00
manage_tags_btn = memnew ( Button ) ;
manage_tags_btn - > set_text ( TTR ( " Manage Tags " ) ) ;
tree_vb - > add_child ( manage_tags_btn ) ;
2020-08-27 15:43:49 +02:00
erase_btn = memnew ( Button ) ;
erase_btn - > set_text ( TTR ( " Remove " ) ) ;
2021-08-13 23:31:57 +02:00
erase_btn - > set_shortcut ( ED_SHORTCUT ( " project_manager/remove_project " , TTR ( " Remove Project " ) , Key : : KEY_DELETE ) ) ;
2020-08-27 15:43:49 +02:00
erase_btn - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _erase_project ) ) ;
tree_vb - > add_child ( erase_btn ) ;
2014-11-01 07:06:29 +01:00
2020-08-27 15:43:49 +02:00
erase_missing_btn = memnew ( Button ) ;
erase_missing_btn - > set_text ( TTR ( " Remove Missing " ) ) ;
erase_missing_btn - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _erase_missing_projects ) ) ;
tree_vb - > add_child ( erase_missing_btn ) ;
2021-04-08 01:05:47 +02:00
tree_vb - > add_spacer ( ) ;
about_btn = memnew ( Button ) ;
about_btn - > set_text ( TTR ( " About " ) ) ;
about_btn - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _show_about ) ) ;
tree_vb - > add_child ( about_btn ) ;
2014-11-01 07:06:29 +01:00
}
2020-08-27 15:43:49 +02:00
{
// Version info and language options
2020-09-03 13:22:16 +02:00
settings_hb = memnew ( HBoxContainer ) ;
2021-11-25 03:58:47 +01:00
settings_hb - > set_alignment ( BoxContainer : : ALIGNMENT_END ) ;
2020-08-27 15:43:49 +02:00
settings_hb - > set_h_grow_direction ( Control : : GROW_DIRECTION_BEGIN ) ;
2020-12-22 17:24:29 +01:00
settings_hb - > set_anchors_and_offsets_preset ( Control : : PRESET_TOP_RIGHT ) ;
2020-08-27 15:43:49 +02:00
2019-12-24 01:20:54 +01:00
// A VBoxContainer that contains a dummy Control node to adjust the LinkButton's vertical position.
VBoxContainer * spacer_vb = memnew ( VBoxContainer ) ;
settings_hb - > add_child ( spacer_vb ) ;
Control * v_spacer = memnew ( Control ) ;
spacer_vb - > add_child ( v_spacer ) ;
version_btn = memnew ( LinkButton ) ;
2020-08-27 15:43:49 +02:00
String hash = String ( VERSION_HASH ) ;
if ( hash . length ( ) ! = 0 ) {
2021-06-07 23:51:18 +02:00
hash = " " + vformat ( " [%s] " , hash . left ( 9 ) ) ;
2020-08-27 15:43:49 +02:00
}
2019-12-24 01:20:54 +01:00
version_btn - > set_text ( " v " VERSION_FULL_BUILD + hash ) ;
// Fade the version label to be less prominent, but still readable.
version_btn - > set_self_modulate ( Color ( 1 , 1 , 1 , 0.6 ) ) ;
version_btn - > set_underline_mode ( LinkButton : : UNDERLINE_MODE_ON_HOVER ) ;
2022-08-25 12:42:17 +02:00
version_btn - > set_tooltip_text ( TTR ( " Click to copy. " ) ) ;
2019-12-24 01:20:54 +01:00
version_btn - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _version_button_pressed ) ) ;
spacer_vb - > add_child ( version_btn ) ;
// Add a small horizontal spacer between the version and language buttons
// to distinguish them.
Control * h_spacer = memnew ( Control ) ;
settings_hb - > add_child ( h_spacer ) ;
2020-08-27 15:43:49 +02:00
language_btn = memnew ( OptionButton ) ;
language_btn - > set_focus_mode ( Control : : FOCUS_NONE ) ;
2022-09-13 02:59:16 +02:00
language_btn - > set_fit_to_longest_item ( false ) ;
language_btn - > set_flat ( true ) ;
2020-08-27 15:43:49 +02:00
language_btn - > connect ( " item_selected " , callable_mp ( this , & ProjectManager : : _language_selected ) ) ;
2022-05-20 03:08:01 +02:00
# ifdef ANDROID_ENABLED
// The language selection dropdown doesn't work on Android (as the setting isn't saved), see GH-60353.
// Also, the dropdown it spawns is very tall and can't be scrolled without a hardware mouse.
// Hiding the language selection dropdown also leaves more space for the version label to display.
language_btn - > hide ( ) ;
# endif
2020-08-27 15:43:49 +02:00
Vector < String > editor_languages ;
List < PropertyInfo > editor_settings_properties ;
EditorSettings : : get_singleton ( ) - > get_property_list ( & editor_settings_properties ) ;
2021-07-24 15:46:25 +02:00
for ( const PropertyInfo & pi : editor_settings_properties ) {
2020-08-27 15:43:49 +02:00
if ( pi . name = = " interface/editor/editor_language " ) {
editor_languages = pi . hint_string . split ( " , " ) ;
break ;
}
}
2017-09-11 07:01:20 +02:00
2022-10-18 16:43:37 +02:00
String current_lang = EDITOR_GET ( " interface/editor/editor_language " ) ;
2020-08-27 15:43:49 +02:00
language_btn - > set_text ( current_lang ) ;
2017-11-26 19:31:08 +01:00
2020-08-27 15:43:49 +02:00
for ( int i = 0 ; i < editor_languages . size ( ) ; i + + ) {
2023-11-18 23:40:56 +01:00
const String & lang = editor_languages [ i ] ;
2020-08-27 15:43:49 +02:00
String lang_name = TranslationServer : : get_singleton ( ) - > get_locale_name ( lang ) ;
2021-11-30 13:13:39 +01:00
language_btn - > add_item ( vformat ( " [%s] %s " , lang , lang_name ) , i ) ;
2020-08-27 15:43:49 +02:00
language_btn - > set_item_metadata ( i , lang ) ;
if ( current_lang = = lang ) {
language_btn - > select ( i ) ;
}
}
2014-02-10 02:10:30 +01:00
2020-08-27 15:43:49 +02:00
settings_hb - > add_child ( language_btn ) ;
center_box - > add_child ( settings_hb ) ;
2020-05-14 16:41:43 +02:00
}
2014-06-11 11:57:13 +02:00
2022-06-29 19:27:27 +02:00
if ( AssetLibraryEditorPlugin : : is_available ( ) ) {
2020-08-27 15:43:49 +02:00
asset_library = memnew ( EditorAssetLibrary ( true ) ) ;
2021-04-13 22:58:09 +02:00
asset_library - > set_name ( TTR ( " Asset Library Projects " ) ) ;
2020-08-27 15:43:49 +02:00
tabs - > add_child ( asset_library ) ;
asset_library - > connect ( " install_asset " , callable_mp ( this , & ProjectManager : : _install_project ) ) ;
} else {
2022-07-08 15:25:54 +02:00
print_verbose ( " Asset Library not available (due to using Web editor, or SSL support disabled). " ) ;
2020-05-14 16:41:43 +02:00
}
2014-06-11 11:57:13 +02:00
2020-08-27 15:43:49 +02:00
{
// Dialogs
language_restart_ask = memnew ( ConfirmationDialog ) ;
2022-07-08 02:31:19 +02:00
language_restart_ask - > set_ok_button_text ( TTR ( " Restart Now " ) ) ;
2020-12-14 19:37:30 +01:00
language_restart_ask - > get_ok_button ( ) - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _restart_confirm ) ) ;
2022-07-08 02:31:19 +02:00
language_restart_ask - > set_cancel_button_text ( TTR ( " Continue " ) ) ;
2020-08-27 15:43:49 +02:00
add_child ( language_restart_ask ) ;
2022-02-03 01:21:52 +01:00
scan_dir = memnew ( EditorFileDialog ) ;
scan_dir - > set_previews_enabled ( false ) ;
scan_dir - > set_access ( EditorFileDialog : : ACCESS_FILESYSTEM ) ;
scan_dir - > set_file_mode ( EditorFileDialog : : FILE_MODE_OPEN_DIR ) ;
2020-08-27 15:43:49 +02:00
scan_dir - > set_title ( TTR ( " Select a Folder to Scan " ) ) ; // must be after mode or it's overridden
2022-10-18 16:43:37 +02:00
scan_dir - > set_current_dir ( EDITOR_GET ( " filesystem/directories/default_project_path " ) ) ;
2020-08-27 15:43:49 +02:00
add_child ( scan_dir ) ;
2023-12-20 13:09:36 +01:00
scan_dir - > connect ( " dir_selected " , callable_mp ( _project_list , & ProjectList : : find_projects ) ) ;
2020-08-27 15:43:49 +02:00
erase_missing_ask = memnew ( ConfirmationDialog ) ;
2022-07-08 02:31:19 +02:00
erase_missing_ask - > set_ok_button_text ( TTR ( " Remove All " ) ) ;
2020-12-14 19:37:30 +01:00
erase_missing_ask - > get_ok_button ( ) - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _erase_missing_projects_confirm ) ) ;
2020-08-27 15:43:49 +02:00
add_child ( erase_missing_ask ) ;
erase_ask = memnew ( ConfirmationDialog ) ;
2022-07-08 02:31:19 +02:00
erase_ask - > set_ok_button_text ( TTR ( " Remove " ) ) ;
2020-12-14 19:37:30 +01:00
erase_ask - > get_ok_button ( ) - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _erase_project_confirm ) ) ;
2020-08-27 15:43:49 +02:00
add_child ( erase_ask ) ;
2021-03-20 19:31:49 +01:00
VBoxContainer * erase_ask_vb = memnew ( VBoxContainer ) ;
erase_ask - > add_child ( erase_ask_vb ) ;
erase_ask_label = memnew ( Label ) ;
erase_ask_vb - > add_child ( erase_ask_label ) ;
2023-03-15 23:48:52 +01:00
// Comment out for now until we have a better warning system to
// ensure users delete their project only.
//delete_project_contents = memnew(CheckBox);
//delete_project_contents->set_text(TTR("Also delete project contents (no undo!)"));
//erase_ask_vb->add_child(delete_project_contents);
2021-03-20 19:31:49 +01:00
2020-08-27 15:43:49 +02:00
multi_open_ask = memnew ( ConfirmationDialog ) ;
2022-07-08 02:31:19 +02:00
multi_open_ask - > set_ok_button_text ( TTR ( " Edit " ) ) ;
2020-12-14 19:37:30 +01:00
multi_open_ask - > get_ok_button ( ) - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _open_selected_projects ) ) ;
2020-08-27 15:43:49 +02:00
add_child ( multi_open_ask ) ;
multi_run_ask = memnew ( ConfirmationDialog ) ;
2022-07-08 02:31:19 +02:00
multi_run_ask - > set_ok_button_text ( TTR ( " Run " ) ) ;
2020-12-14 19:37:30 +01:00
multi_run_ask - > get_ok_button ( ) - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _run_project_confirm ) ) ;
2020-08-27 15:43:49 +02:00
add_child ( multi_run_ask ) ;
ask_update_settings = memnew ( ConfirmationDialog ) ;
2022-09-15 00:45:59 +02:00
ask_update_settings - > set_autowrap ( true ) ;
2020-12-14 19:37:30 +01:00
ask_update_settings - > get_ok_button ( ) - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _confirm_update_settings ) ) ;
2023-03-02 07:13:33 +01:00
full_convert_button = ask_update_settings - > add_button ( TTR ( " Convert Full Project " ) , ! GLOBAL_GET ( " gui/common/swap_cancel_ok " ) ) ;
2022-09-15 00:45:59 +02:00
full_convert_button - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _full_convert_button_pressed ) ) ;
2020-08-27 15:43:49 +02:00
add_child ( ask_update_settings ) ;
2022-08-26 15:22:30 +02:00
ask_full_convert_dialog = memnew ( ConfirmationDialog ) ;
ask_full_convert_dialog - > set_autowrap ( true ) ;
2023-07-10 13:08:11 +02:00
ask_full_convert_dialog - > set_text ( TTR ( " This option will perform full project conversion, updating scenes, resources and scripts from Godot 3 to work in Godot 4. \n \n Note that this is a best-effort conversion, i.e. it makes upgrading the project easier, but it will not open out-of-the-box and will still require manual adjustments. \n \n IMPORTANT: Make sure to backup your project before converting, as this operation makes it impossible to open it in older versions of Godot. " ) ) ;
2022-08-26 15:22:30 +02:00
ask_full_convert_dialog - > connect ( " confirmed " , callable_mp ( this , & ProjectManager : : _perform_full_project_conversion ) ) ;
add_child ( ask_full_convert_dialog ) ;
2020-08-27 15:43:49 +02:00
npdialog = memnew ( ProjectDialog ) ;
npdialog - > connect ( " projects_updated " , callable_mp ( this , & ProjectManager : : _on_projects_updated ) ) ;
npdialog - > connect ( " project_created " , callable_mp ( this , & ProjectManager : : _on_project_created ) ) ;
add_child ( npdialog ) ;
run_error_diag = memnew ( AcceptDialog ) ;
run_error_diag - > set_title ( TTR ( " Can't run project " ) ) ;
add_child ( run_error_diag ) ;
2018-12-12 21:12:41 +01:00
2020-08-27 15:43:49 +02:00
dialog_error = memnew ( AcceptDialog ) ;
add_child ( dialog_error ) ;
2014-06-11 11:57:13 +02:00
2022-05-25 13:09:53 +02:00
if ( asset_library ) {
open_templates = memnew ( ConfirmationDialog ) ;
open_templates - > set_text ( TTR ( " You currently don't have any projects. \n Would you like to explore official example projects in the Asset Library? " ) ) ;
2022-07-08 02:31:19 +02:00
open_templates - > set_ok_button_text ( TTR ( " Open Asset Library " ) ) ;
2022-05-25 13:09:53 +02:00
open_templates - > connect ( " confirmed " , callable_mp ( this , & ProjectManager : : _open_asset_library ) ) ;
add_child ( open_templates ) ;
}
2021-04-08 01:05:47 +02:00
about = memnew ( EditorAbout ) ;
add_child ( about ) ;
2022-02-03 01:21:52 +01:00
_build_icon_type_cache ( get_theme ( ) ) ;
2018-07-26 13:45:38 +02:00
}
2019-08-13 19:16:17 +02:00
2023-03-17 23:30:21 +01:00
{
// Tag management.
tag_manage_dialog = memnew ( ConfirmationDialog ) ;
add_child ( tag_manage_dialog ) ;
tag_manage_dialog - > set_title ( TTR ( " Manage Project Tags " ) ) ;
tag_manage_dialog - > get_ok_button ( ) - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _apply_project_tags ) ) ;
manage_tags_btn - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _manage_project_tags ) ) ;
VBoxContainer * tag_vb = memnew ( VBoxContainer ) ;
tag_manage_dialog - > add_child ( tag_vb ) ;
Label * label = memnew ( Label ( TTR ( " Project Tags " ) ) ) ;
tag_vb - > add_child ( label ) ;
label - > set_theme_type_variation ( " HeaderMedium " ) ;
label - > set_horizontal_alignment ( HORIZONTAL_ALIGNMENT_CENTER ) ;
label = memnew ( Label ( TTR ( " Click tag to remove it from the project. " ) ) ) ;
tag_vb - > add_child ( label ) ;
label - > set_horizontal_alignment ( HORIZONTAL_ALIGNMENT_CENTER ) ;
project_tags = memnew ( HFlowContainer ) ;
tag_vb - > add_child ( project_tags ) ;
project_tags - > set_custom_minimum_size ( Vector2 ( 0 , 100 ) * EDSCALE ) ;
tag_vb - > add_child ( memnew ( HSeparator ) ) ;
label = memnew ( Label ( TTR ( " All Tags " ) ) ) ;
tag_vb - > add_child ( label ) ;
label - > set_theme_type_variation ( " HeaderMedium " ) ;
label - > set_horizontal_alignment ( HORIZONTAL_ALIGNMENT_CENTER ) ;
label = memnew ( Label ( TTR ( " Click tag to add it to the project. " ) ) ) ;
tag_vb - > add_child ( label ) ;
label - > set_horizontal_alignment ( HORIZONTAL_ALIGNMENT_CENTER ) ;
all_tags = memnew ( HFlowContainer ) ;
tag_vb - > add_child ( all_tags ) ;
all_tags - > set_custom_minimum_size ( Vector2 ( 0 , 100 ) * EDSCALE ) ;
tag_edit_error = memnew ( Label ) ;
tag_vb - > add_child ( tag_edit_error ) ;
tag_edit_error - > set_autowrap_mode ( TextServer : : AUTOWRAP_WORD ) ;
create_tag_dialog = memnew ( ConfirmationDialog ) ;
tag_manage_dialog - > add_child ( create_tag_dialog ) ;
create_tag_dialog - > set_title ( TTR ( " Create New Tag " ) ) ;
create_tag_dialog - > get_ok_button ( ) - > connect ( " pressed " , callable_mp ( this , & ProjectManager : : _create_new_tag ) ) ;
tag_vb = memnew ( VBoxContainer ) ;
create_tag_dialog - > add_child ( tag_vb ) ;
Label * info = memnew ( Label ( TTR ( " Tags are capitalized automatically when displayed. " ) ) ) ;
tag_vb - > add_child ( info ) ;
new_tag_name = memnew ( LineEdit ) ;
tag_vb - > add_child ( new_tag_name ) ;
new_tag_name - > connect ( " text_changed " , callable_mp ( this , & ProjectManager : : _set_new_tag_name ) ) ;
new_tag_name - > connect ( " text_submitted " , callable_mp ( this , & ProjectManager : : _create_new_tag ) . unbind ( 1 ) ) ;
create_tag_dialog - > connect ( " about_to_popup " , callable_mp ( new_tag_name , & LineEdit : : clear ) ) ;
create_tag_dialog - > connect ( " about_to_popup " , callable_mp ( ( Control * ) new_tag_name , & Control : : grab_focus ) , CONNECT_DEFERRED ) ;
tag_error = memnew ( Label ) ;
tag_vb - > add_child ( tag_error ) ;
create_tag_btn = memnew ( Button ) ;
all_tags - > add_child ( create_tag_btn ) ;
create_tag_btn - > connect ( " pressed " , callable_mp ( ( Window * ) create_tag_dialog , & Window : : popup_centered ) . bind ( Vector2i ( 500 , 0 ) * EDSCALE ) ) ;
}
2023-12-20 13:09:36 +01:00
// Initialize project list.
{
Ref < DirAccess > dir_access = DirAccess : : create ( DirAccess : : AccessType : : ACCESS_FILESYSTEM ) ;
2021-02-01 23:19:59 +01:00
2023-12-20 13:09:36 +01:00
String default_project_path = EDITOR_GET ( " filesystem/directories/default_project_path " ) ;
if ( ! default_project_path . is_empty ( ) & & ! dir_access - > dir_exists ( default_project_path ) ) {
Error error = dir_access - > make_dir_recursive ( default_project_path ) ;
if ( error ! = OK ) {
ERR_PRINT ( " Could not create default project directory at: " + default_project_path ) ;
}
2021-02-01 23:19:59 +01:00
}
2023-12-20 13:09:36 +01:00
bool scanned_for_projects = false ; // Scanning will update the list automatically.
String autoscan_path = EDITOR_GET ( " filesystem/directories/autoscan_project_path " ) ;
if ( ! autoscan_path . is_empty ( ) ) {
if ( dir_access - > dir_exists ( autoscan_path ) ) {
_project_list - > find_projects ( autoscan_path ) ;
scanned_for_projects = true ;
} else {
Error error = dir_access - > make_dir_recursive ( autoscan_path ) ;
if ( error ! = OK ) {
ERR_PRINT ( " Could not create project autoscan directory at: " + autoscan_path ) ;
}
2021-02-01 23:19:59 +01:00
}
}
2023-12-20 13:09:36 +01:00
if ( ! scanned_for_projects ) {
_project_list - > update_project_list ( ) ;
}
2020-08-27 15:43:49 +02:00
}
2018-11-23 20:15:33 +01:00
2020-08-27 15:43:49 +02:00
SceneTree : : get_singleton ( ) - > get_root ( ) - > connect ( " files_dropped " , callable_mp ( this , & ProjectManager : : _files_dropped ) ) ;
2018-11-23 20:15:33 +01:00
2020-08-27 15:43:49 +02:00
OS : : get_singleton ( ) - > set_low_processor_usage_mode ( true ) ;
2023-12-20 13:19:21 +01:00
_update_size_limits ( ) ;
2014-06-11 11:57:13 +02:00
}
2019-02-18 23:03:54 +01:00
2020-08-27 15:43:49 +02:00
ProjectManager : : ~ ProjectManager ( ) {
2022-02-12 20:55:11 +01:00
singleton = nullptr ;
2020-08-27 15:43:49 +02:00
if ( EditorSettings : : get_singleton ( ) ) {
EditorSettings : : destroy ( ) ;
2019-02-18 23:03:54 +01:00
}
2023-10-18 17:36:20 +02:00
EditorColorMap : : finish ( ) ;
EditorTheme : : finalize ( ) ;
2019-02-18 23:03:54 +01:00
}
2023-03-17 23:30:21 +01:00
void ProjectTag : : _notification ( int p_what ) {
if ( display_close & & p_what = = NOTIFICATION_THEME_CHANGED ) {
button - > set_icon ( get_theme_icon ( SNAME ( " close " ) , SNAME ( " TabBar " ) ) ) ;
}
}
ProjectTag : : ProjectTag ( const String & p_text , bool p_display_close ) {
add_theme_constant_override ( SNAME ( " separation " ) , 0 ) ;
set_v_size_flags ( SIZE_SHRINK_CENTER ) ;
tag_string = p_text ;
display_close = p_display_close ;
Color tag_color = Color ( 1 , 0 , 0 ) ;
tag_color . set_ok_hsl_s ( 0.8 ) ;
tag_color . set_ok_hsl_h ( float ( p_text . hash ( ) * 10001 % UINT32_MAX ) / float ( UINT32_MAX ) ) ;
set_self_modulate ( tag_color ) ;
ColorRect * cr = memnew ( ColorRect ) ;
add_child ( cr ) ;
cr - > set_custom_minimum_size ( Vector2 ( 4 , 0 ) * EDSCALE ) ;
cr - > set_color ( tag_color ) ;
button = memnew ( Button ) ;
add_child ( button ) ;
2023-06-09 12:18:53 +02:00
button - > set_auto_translate ( false ) ;
2023-03-17 23:30:21 +01:00
button - > set_text ( p_text . capitalize ( ) ) ;
button - > set_focus_mode ( FOCUS_NONE ) ;
button - > set_icon_alignment ( HORIZONTAL_ALIGNMENT_RIGHT ) ;
button - > set_theme_type_variation ( SNAME ( " ProjectTag " ) ) ;
}
void ProjectTag : : connect_button_to ( const Callable & p_callable ) {
button - > connect ( SNAME ( " pressed " ) , p_callable , CONNECT_DEFERRED ) ;
}
const String ProjectTag : : get_tag ( ) const {
return tag_string ;
}