2017-10-03 05:24:00 +08:00
/*************************************************************************/
/* godotsharp_editor.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
2018-01-01 21:40:08 +08:00
/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
2017-10-03 05:24:00 +08:00
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
2018-01-05 07:50:27 +08:00
2017-10-03 05:24:00 +08:00
# include "godotsharp_editor.h"
# include "core/os/os.h"
# include "core/project_settings.h"
# include "scene/gui/control.h"
# include "scene/main/node.h"
# include "../csharp_script.h"
# include "../godotsharp_dirs.h"
# include "../mono_gd/gd_mono.h"
2018-09-17 22:40:26 +08:00
# include "../mono_gd/gd_mono_marshal.h"
2017-10-03 05:24:00 +08:00
# include "../utils/path_utils.h"
# include "bindings_generator.h"
# include "csharp_project.h"
2018-02-22 20:39:41 +08:00
# include "godotsharp_export.h"
2017-10-03 05:24:00 +08:00
# include "net_solution.h"
2018-09-17 22:40:26 +08:00
# ifdef OSX_ENABLED
# include "../utils/osx_utils.h"
# endif
2017-10-03 05:24:00 +08:00
# ifdef WINDOWS_ENABLED
# include "../utils/mono_reg_utils.h"
# endif
GodotSharpEditor * GodotSharpEditor : : singleton = NULL ;
bool GodotSharpEditor : : _create_project_solution ( ) {
2017-12-23 22:40:15 +08:00
EditorProgress pr ( " create_csharp_solution " , TTR ( " Generating solution... " ) , 2 ) ;
2017-10-03 05:24:00 +08:00
2017-12-23 22:40:15 +08:00
pr . step ( TTR ( " Generating C# project... " ) ) ;
2017-10-03 05:24:00 +08:00
String path = OS : : get_singleton ( ) - > get_resource_dir ( ) ;
String name = ProjectSettings : : get_singleton ( ) - > get ( " application/config/name " ) ;
2017-10-28 03:22:54 +08:00
if ( name . empty ( ) ) {
name = " UnnamedProject " ;
}
2017-10-03 05:24:00 +08:00
String guid = CSharpProject : : generate_game_project ( path , name ) ;
if ( guid . length ( ) ) {
NETSolution solution ( name ) ;
if ( ! solution . set_path ( path ) ) {
2017-12-23 22:40:15 +08:00
show_error_dialog ( TTR ( " Failed to create solution. " ) ) ;
2017-10-03 05:24:00 +08:00
return false ;
}
Vector < String > extra_configs ;
extra_configs . push_back ( " Tools " ) ;
solution . add_new_project ( name , guid , extra_configs ) ;
Error sln_error = solution . save ( ) ;
if ( sln_error ! = OK ) {
2017-12-23 22:40:15 +08:00
show_error_dialog ( TTR ( " Failed to save solution. " ) ) ;
2017-10-03 05:24:00 +08:00
return false ;
}
2018-02-22 20:13:51 +08:00
if ( ! GodotSharpBuilds : : make_api_sln ( APIAssembly : : API_CORE ) )
2017-10-03 05:24:00 +08:00
return false ;
2018-02-22 20:13:51 +08:00
if ( ! GodotSharpBuilds : : make_api_sln ( APIAssembly : : API_EDITOR ) )
2017-10-03 05:24:00 +08:00
return false ;
2017-12-23 22:40:15 +08:00
pr . step ( TTR ( " Done " ) ) ;
2017-10-03 05:24:00 +08:00
// Here, after all calls to progress_task_step
call_deferred ( " _remove_create_sln_menu_option " ) ;
} else {
2017-12-23 22:40:15 +08:00
show_error_dialog ( TTR ( " Failed to create C# project. " ) ) ;
2017-10-03 05:24:00 +08:00
}
return true ;
}
void GodotSharpEditor : : _remove_create_sln_menu_option ( ) {
menu_popup - > remove_item ( menu_popup - > get_item_index ( MENU_CREATE_SLN ) ) ;
if ( menu_popup - > get_item_count ( ) = = 0 )
menu_button - > hide ( ) ;
bottom_panel_btn - > show ( ) ;
}
2018-01-22 21:57:10 +08:00
void GodotSharpEditor : : _show_about_dialog ( ) {
bool show_on_start = EDITOR_GET ( " mono/editor/show_info_on_start " ) ;
about_dialog_checkbox - > set_pressed ( show_on_start ) ;
about_dialog - > popup_centered_minsize ( ) ;
}
void GodotSharpEditor : : _toggle_about_dialog_on_start ( bool p_enabled ) {
bool show_on_start = EDITOR_GET ( " mono/editor/show_info_on_start " ) ;
if ( show_on_start ! = p_enabled ) {
EditorSettings : : get_singleton ( ) - > set_setting ( " mono/editor/show_info_on_start " , p_enabled ) ;
}
}
2017-10-03 05:24:00 +08:00
void GodotSharpEditor : : _menu_option_pressed ( int p_id ) {
switch ( p_id ) {
case MENU_CREATE_SLN : {
_create_project_solution ( ) ;
} break ;
2018-01-22 21:57:10 +08:00
case MENU_ABOUT_CSHARP : {
_show_about_dialog ( ) ;
} break ;
2017-10-03 05:24:00 +08:00
default :
ERR_FAIL ( ) ;
}
}
2018-01-22 21:57:10 +08:00
void GodotSharpEditor : : _notification ( int p_notification ) {
switch ( p_notification ) {
case NOTIFICATION_READY : {
bool show_info_dialog = EDITOR_GET ( " mono/editor/show_info_on_start " ) ;
if ( show_info_dialog ) {
about_dialog - > set_exclusive ( true ) ;
_show_about_dialog ( ) ;
// Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive then.
about_dialog - > set_exclusive ( false ) ;
}
}
}
}
2017-10-03 05:24:00 +08:00
void GodotSharpEditor : : _bind_methods ( ) {
ClassDB : : bind_method ( D_METHOD ( " _create_project_solution " ) , & GodotSharpEditor : : _create_project_solution ) ;
ClassDB : : bind_method ( D_METHOD ( " _remove_create_sln_menu_option " ) , & GodotSharpEditor : : _remove_create_sln_menu_option ) ;
2018-01-22 21:57:10 +08:00
ClassDB : : bind_method ( D_METHOD ( " _toggle_about_dialog_on_start " ) , & GodotSharpEditor : : _toggle_about_dialog_on_start ) ;
2017-10-03 05:24:00 +08:00
ClassDB : : bind_method ( D_METHOD ( " _menu_option_pressed " , " id " ) , & GodotSharpEditor : : _menu_option_pressed ) ;
}
2018-09-17 22:40:26 +08:00
MonoBoolean godot_icall_MonoDevelopInstance_IsApplicationBundleInstalled ( MonoString * p_bundle_id ) {
# ifdef OSX_ENABLED
return ( MonoBoolean ) osx_is_app_bundle_installed ( GDMonoMarshal : : mono_string_to_godot ( p_bundle_id ) ) ;
# else
( void ) p_bundle_id ; // UNUSED
ERR_FAIL_V ( false ) ;
# endif
}
2018-09-18 04:54:47 +08:00
MonoString * godot_icall_Utils_OS_GetPlatformName ( ) {
return GDMonoMarshal : : mono_string_from_godot ( OS : : get_singleton ( ) - > get_name ( ) ) ;
}
2018-09-17 22:40:26 +08:00
void GodotSharpEditor : : register_internal_calls ( ) {
static bool registered = false ;
ERR_FAIL_COND ( registered ) ;
registered = true ;
mono_add_internal_call ( " GodotSharpTools.Editor.MonoDevelopInstance::IsApplicationBundleInstalled " , ( void * ) godot_icall_MonoDevelopInstance_IsApplicationBundleInstalled ) ;
2018-09-18 04:54:47 +08:00
mono_add_internal_call ( " GodotSharpTools.Utils.OS::GetPlatformName " , ( void * ) godot_icall_Utils_OS_GetPlatformName ) ;
2018-09-17 22:40:26 +08:00
GodotSharpBuilds : : register_internal_calls ( ) ;
2018-10-04 01:01:57 +08:00
GodotSharpExport : : register_internal_calls ( ) ;
2018-09-17 22:40:26 +08:00
}
2017-10-03 05:24:00 +08:00
void GodotSharpEditor : : show_error_dialog ( const String & p_message , const String & p_title ) {
error_dialog - > set_title ( p_title ) ;
error_dialog - > set_text ( p_message ) ;
error_dialog - > popup_centered_minsize ( ) ;
}
Error GodotSharpEditor : : open_in_external_editor ( const Ref < Script > & p_script , int p_line , int p_col ) {
ExternalEditor editor = ExternalEditor ( int ( EditorSettings : : get_singleton ( ) - > get ( " mono/editor/external_editor " ) ) ) ;
switch ( editor ) {
2018-09-17 22:40:26 +08:00
case EDITOR_VSCODE : {
static String vscode_path ;
if ( vscode_path . empty ( ) | | ! FileAccess : : exists ( vscode_path ) ) {
// Try to search it again if it wasn't found last time or if it was removed from its location
vscode_path = path_which ( " code " ) ;
}
2017-10-03 05:24:00 +08:00
List < String > args ;
2018-09-17 22:40:26 +08:00
# ifdef OSX_ENABLED
// The package path is '/Applications/Visual Studio Code.app'
static const String vscode_bundle_id = " com.microsoft.VSCode " ;
static bool osx_app_bundle_installed = osx_is_app_bundle_installed ( vscode_bundle_id ) ;
if ( osx_app_bundle_installed ) {
args . push_back ( " -b " ) ;
args . push_back ( vscode_bundle_id ) ;
// The reusing of existing windows made by the 'open' command might not choose a wubdiw that is
// editing our folder. It's better to ask for a new window and let VSCode do the window management.
args . push_back ( " -n " ) ;
// The open process must wait until the application finishes (which is instant in VSCode's case)
args . push_back ( " --wait-apps " ) ;
args . push_back ( " --args " ) ;
}
# endif
2017-10-03 05:24:00 +08:00
args . push_back ( ProjectSettings : : get_singleton ( ) - > get_resource_path ( ) ) ;
String script_path = ProjectSettings : : get_singleton ( ) - > globalize_path ( p_script - > get_path ( ) ) ;
if ( p_line > = 0 ) {
args . push_back ( " -g " ) ;
2018-01-10 00:19:37 +08:00
args . push_back ( script_path + " : " + itos ( p_line + 1 ) + " : " + itos ( p_col ) ) ;
2017-10-03 05:24:00 +08:00
} else {
args . push_back ( script_path ) ;
}
2018-09-17 22:40:26 +08:00
# ifdef OSX_ENABLED
ERR_EXPLAIN ( " Cannot find code editor: VSCode " ) ;
ERR_FAIL_COND_V ( ! osx_app_bundle_installed & & vscode_path . empty ( ) , ERR_FILE_NOT_FOUND ) ;
2017-10-03 05:24:00 +08:00
2018-09-17 22:40:26 +08:00
String command = osx_app_bundle_installed ? " /usr/bin/open " : vscode_path ;
# else
ERR_EXPLAIN ( " Cannot find code editor: VSCode " ) ;
ERR_FAIL_COND_V ( vscode_path . empty ( ) , ERR_FILE_NOT_FOUND ) ;
String command = vscode_path ;
# endif
Error err = OS : : get_singleton ( ) - > execute ( command , args , false ) ;
2017-10-03 05:24:00 +08:00
if ( err ! = OK ) {
2018-09-17 22:40:26 +08:00
ERR_PRINT ( " Error when trying to execute code editor: VSCode " ) ;
2017-10-03 05:24:00 +08:00
return err ;
}
} break ;
2018-09-17 22:40:26 +08:00
# ifdef OSX_ENABLED
case EDITOR_VISUALSTUDIO_MAC :
// [[fallthrough]];
# endif
2017-10-03 05:24:00 +08:00
case EDITOR_MONODEVELOP : {
2018-09-17 22:40:26 +08:00
# ifdef OSX_ENABLED
bool is_visualstudio = editor = = EDITOR_VISUALSTUDIO_MAC ;
MonoDevelopInstance * * instance = is_visualstudio ?
& visualstudio_mac_instance :
& monodevelop_instance ;
MonoDevelopInstance : : EditorId editor_id = is_visualstudio ?
MonoDevelopInstance : : VISUALSTUDIO_FOR_MAC :
MonoDevelopInstance : : MONODEVELOP ;
# else
MonoDevelopInstance * * instance = & monodevelop_instance ;
MonoDevelopInstance : : EditorId editor_id = MonoDevelopInstance : : MONODEVELOP ;
# endif
if ( ! * instance )
* instance = memnew ( MonoDevelopInstance ( GodotSharpDirs : : get_project_sln_path ( ) , editor_id ) ) ;
2017-10-03 05:24:00 +08:00
String script_path = ProjectSettings : : get_singleton ( ) - > globalize_path ( p_script - > get_path ( ) ) ;
2018-01-10 00:19:37 +08:00
if ( p_line > = 0 ) {
script_path + = " ; " + itos ( p_line + 1 ) + " ; " + itos ( p_col ) ;
}
2018-09-17 22:40:26 +08:00
( * instance ) - > execute ( script_path ) ;
2017-10-03 05:24:00 +08:00
} break ;
default :
return ERR_UNAVAILABLE ;
}
return OK ;
}
bool GodotSharpEditor : : overrides_external_editor ( ) {
return ExternalEditor ( int ( EditorSettings : : get_singleton ( ) - > get ( " mono/editor/external_editor " ) ) ) ! = EDITOR_NONE ;
}
GodotSharpEditor : : GodotSharpEditor ( EditorNode * p_editor ) {
singleton = this ;
2018-09-17 22:40:26 +08:00
monodevelop_instance = NULL ;
# ifdef OSX_ENABLED
visualstudio_mac_instance = NULL ;
# endif
2017-10-03 05:24:00 +08:00
editor = p_editor ;
error_dialog = memnew ( AcceptDialog ) ;
editor - > get_gui_base ( ) - > add_child ( error_dialog ) ;
2017-12-23 22:40:15 +08:00
bottom_panel_btn = editor - > add_bottom_panel_item ( TTR ( " Mono " ) , memnew ( MonoBottomPanel ( editor ) ) ) ;
2017-10-03 05:24:00 +08:00
godotsharp_builds = memnew ( GodotSharpBuilds ) ;
editor - > add_child ( memnew ( MonoReloadNode ) ) ;
menu_button = memnew ( MenuButton ) ;
2017-12-23 22:40:15 +08:00
menu_button - > set_text ( TTR ( " Mono " ) ) ;
2017-10-03 05:24:00 +08:00
menu_popup = menu_button - > get_popup ( ) ;
2018-01-22 21:57:10 +08:00
// TODO: Remove or edit this info dialog once Mono support is no longer in alpha
{
menu_popup - > add_item ( TTR ( " About C# support " ) , MENU_ABOUT_CSHARP ) ;
about_dialog = memnew ( AcceptDialog ) ;
editor - > get_gui_base ( ) - > add_child ( about_dialog ) ;
about_dialog - > set_title ( " Important: C# support is not feature-complete " ) ;
// We don't use set_text() as the default AcceptDialog Label doesn't play well with the TextureRect and CheckBox
// we'll add. Instead we add containers and a new autowrapped Label inside.
// Main VBoxContainer (icon + label on top, checkbox at bottom)
VBoxContainer * about_vbc = memnew ( VBoxContainer ) ;
about_dialog - > add_child ( about_vbc ) ;
// HBoxContainer for icon + label
HBoxContainer * about_hbc = memnew ( HBoxContainer ) ;
about_vbc - > add_child ( about_hbc ) ;
TextureRect * about_icon = memnew ( TextureRect ) ;
about_hbc - > add_child ( about_icon ) ;
Ref < Texture > about_icon_tex = about_icon - > get_icon ( " NodeWarning " , " EditorIcons " ) ;
about_icon - > set_texture ( about_icon_tex ) ;
Label * about_label = memnew ( Label ) ;
about_hbc - > add_child ( about_label ) ;
about_label - > set_custom_minimum_size ( Size2 ( 600 , 150 ) * EDSCALE ) ;
about_label - > set_v_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
about_label - > set_autowrap ( true ) ;
String about_text =
String ( " C# support in Godot Engine is a brand new feature and a work in progress. \n " ) +
2018-07-22 04:26:30 +08:00
" It is currently in an alpha stage and is not suitable for use in production. \n \n " +
" As of Godot 3.1, C# support is not feature-complete and may crash in some situations. " +
" Bugs and usability issues will be addressed gradually over future 3.x releases, " +
2018-02-22 05:46:04 +08:00
" including compatibility breaking changes as new features are implemented for a better overall C# experience. \n \n " +
2018-07-22 04:26:30 +08:00
" If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, Mono version, IDE, etc: \n \n " +
2018-01-22 21:57:10 +08:00
" https://github.com/godotengine/godot/issues \n \n " +
" Your critical feedback at this stage will play a great role in shaping the C# support in future releases, so thank you! " ;
about_label - > set_text ( about_text ) ;
EDITOR_DEF ( " mono/editor/show_info_on_start " , true ) ;
// CheckBox in main container
about_dialog_checkbox = memnew ( CheckBox ) ;
about_vbc - > add_child ( about_dialog_checkbox ) ;
about_dialog_checkbox - > set_text ( " Show this warning when starting the editor " ) ;
about_dialog_checkbox - > connect ( " toggled " , this , " _toggle_about_dialog_on_start " ) ;
}
2017-10-03 05:24:00 +08:00
String sln_path = GodotSharpDirs : : get_project_sln_path ( ) ;
String csproj_path = GodotSharpDirs : : get_project_csproj_path ( ) ;
if ( ! FileAccess : : exists ( sln_path ) | | ! FileAccess : : exists ( csproj_path ) ) {
bottom_panel_btn - > hide ( ) ;
2017-12-23 22:40:15 +08:00
menu_popup - > add_item ( TTR ( " Create C# solution " ) , MENU_CREATE_SLN ) ;
2017-10-03 05:24:00 +08:00
}
menu_popup - > connect ( " id_pressed " , this , " _menu_option_pressed " ) ;
if ( menu_popup - > get_item_count ( ) = = 0 )
menu_button - > hide ( ) ;
editor - > get_menu_hb ( ) - > add_child ( menu_button ) ;
// External editor settings
EditorSettings * ed_settings = EditorSettings : : get_singleton ( ) ;
2017-10-29 05:25:28 +08:00
EDITOR_DEF ( " mono/editor/external_editor " , EDITOR_NONE ) ;
2018-09-17 22:40:26 +08:00
String settings_hint_str = " None " ;
# ifdef WINDOWS_ENABLED
settings_hint_str + = " ,MonoDevelop,Visual Studio Code " ;
# elif OSX_ENABLED
settings_hint_str + = " ,Visual Studio,MonoDevelop,Visual Studio Code " ;
# elif UNIX_ENABLED
settings_hint_str + = " ,MonoDevelop,Visual Studio Code " ;
# endif
ed_settings - > add_property_hint ( PropertyInfo ( Variant : : INT , " mono/editor/external_editor " , PROPERTY_HINT_ENUM , settings_hint_str ) ) ;
2018-02-22 20:39:41 +08:00
// Export plugin
Ref < GodotSharpExport > godotsharp_export ;
godotsharp_export . instance ( ) ;
EditorExport : : get_singleton ( ) - > add_export_plugin ( godotsharp_export ) ;
2017-10-03 05:24:00 +08:00
}
GodotSharpEditor : : ~ GodotSharpEditor ( ) {
singleton = NULL ;
memdelete ( godotsharp_builds ) ;
2018-09-17 22:40:26 +08:00
if ( monodevelop_instance ) {
memdelete ( monodevelop_instance ) ;
monodevelop_instance = NULL ;
2017-10-03 05:24:00 +08:00
}
}
2017-10-29 12:57:20 +08:00
MonoReloadNode * MonoReloadNode : : singleton = NULL ;
void MonoReloadNode : : _reload_timer_timeout ( ) {
CSharpLanguage : : get_singleton ( ) - > reload_assemblies_if_needed ( false ) ;
}
void MonoReloadNode : : restart_reload_timer ( ) {
reload_timer - > stop ( ) ;
reload_timer - > start ( ) ;
}
void MonoReloadNode : : _bind_methods ( ) {
ClassDB : : bind_method ( D_METHOD ( " _reload_timer_timeout " ) , & MonoReloadNode : : _reload_timer_timeout ) ;
}
void MonoReloadNode : : _notification ( int p_what ) {
switch ( p_what ) {
case MainLoop : : NOTIFICATION_WM_FOCUS_IN : {
restart_reload_timer ( ) ;
CSharpLanguage : : get_singleton ( ) - > reload_assemblies_if_needed ( true ) ;
} break ;
default : {
} break ;
} ;
}
MonoReloadNode : : MonoReloadNode ( ) {
singleton = this ;
reload_timer = memnew ( Timer ) ;
add_child ( reload_timer ) ;
reload_timer - > set_one_shot ( false ) ;
reload_timer - > set_wait_time ( EDITOR_DEF ( " mono/assembly_watch_interval_sec " , 0.5 ) ) ;
reload_timer - > connect ( " timeout " , this , " _reload_timer_timeout " ) ;
reload_timer - > start ( ) ;
}
MonoReloadNode : : ~ MonoReloadNode ( ) {
singleton = NULL ;
}