2024-09-18 18:07:18 -03:00
/**************************************************************************/
/* game_view_plugin.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. */
/**************************************************************************/
# include "game_view_plugin.h"
2024-09-25 16:57:23 -04:00
# include "core/config/project_settings.h"
2024-11-06 04:01:58 +01:00
# include "core/debugger/debugger_marshalls.h"
2025-05-29 08:07:49 +03:00
# include "core/debugger/engine_debugger.h"
2025-01-19 15:45:59 -05:00
# include "core/string/translation_server.h"
2024-09-25 16:57:23 -04:00
# include "editor/debugger/editor_debugger_node.h"
2025-01-19 15:45:59 -05:00
# include "editor/debugger/script_editor_debugger.h"
2024-09-25 16:57:23 -04:00
# include "editor/editor_interface.h"
2024-09-18 18:07:18 -03:00
# include "editor/editor_main_screen.h"
# include "editor/editor_node.h"
2025-01-20 18:33:12 -05:00
# include "editor/editor_string_names.h"
2025-02-17 18:14:41 -05:00
# include "editor/gui/editor_bottom_panel.h"
2025-06-10 16:47:26 +02:00
# include "editor/gui/window_wrapper.h"
# include "editor/run/editor_run_bar.h"
# include "editor/run/embedded_process.h"
# include "editor/settings/editor_feature_profile.h"
# include "editor/settings/editor_settings.h"
2024-09-18 18:07:18 -03:00
# include "editor/themes/editor_scale.h"
# include "scene/gui/button.h"
2025-03-21 07:00:49 +00:00
# include "scene/gui/flow_container.h"
2024-09-25 16:57:23 -04:00
# include "scene/gui/label.h"
2024-09-18 18:07:18 -03:00
# include "scene/gui/menu_button.h"
# include "scene/gui/separator.h"
void GameViewDebugger : : _session_started ( Ref < EditorDebuggerSession > p_session ) {
2025-01-10 14:53:41 -05:00
if ( ! is_feature_enabled ) {
return ;
}
2024-11-06 04:01:58 +01:00
Dictionary settings ;
2024-11-18 15:44:38 -03:00
settings [ " debugger/max_node_selection " ] = EDITOR_GET ( " debugger/max_node_selection " ) ;
2024-11-06 04:01:58 +01:00
settings [ " editors/panning/2d_editor_panning_scheme " ] = EDITOR_GET ( " editors/panning/2d_editor_panning_scheme " ) ;
settings [ " editors/panning/simple_panning " ] = EDITOR_GET ( " editors/panning/simple_panning " ) ;
settings [ " editors/panning/warped_mouse_panning " ] = EDITOR_GET ( " editors/panning/warped_mouse_panning " ) ;
settings [ " editors/panning/2d_editor_pan_speed " ] = EDITOR_GET ( " editors/panning/2d_editor_pan_speed " ) ;
2024-11-18 15:44:38 -03:00
settings [ " editors/polygon_editor/point_grab_radius " ] = EDITOR_GET ( " editors/polygon_editor/point_grab_radius " ) ;
2024-11-06 04:01:58 +01:00
settings [ " canvas_item_editor/pan_view " ] = DebuggerMarshalls : : serialize_key_shortcut ( ED_GET_SHORTCUT ( " canvas_item_editor/pan_view " ) ) ;
2024-11-18 15:44:38 -03:00
settings [ " box_selection_fill_color " ] = EditorNode : : get_singleton ( ) - > get_editor_theme ( ) - > get_color ( SNAME ( " box_selection_fill_color " ) , EditorStringName ( Editor ) ) ;
settings [ " box_selection_stroke_color " ] = EditorNode : : get_singleton ( ) - > get_editor_theme ( ) - > get_color ( SNAME ( " box_selection_stroke_color " ) , EditorStringName ( Editor ) ) ;
2025-03-06 23:07:24 -03:00
settings [ " editors/3d/default_fov " ] = EDITOR_GET ( " editors/3d/default_fov " ) ;
settings [ " editors/3d/default_z_near " ] = EDITOR_GET ( " editors/3d/default_z_near " ) ;
settings [ " editors/3d/default_z_far " ] = EDITOR_GET ( " editors/3d/default_z_far " ) ;
settings [ " editors/3d/navigation/invert_x_axis " ] = EDITOR_GET ( " editors/3d/navigation/invert_x_axis " ) ;
settings [ " editors/3d/navigation/invert_y_axis " ] = EDITOR_GET ( " editors/3d/navigation/invert_y_axis " ) ;
settings [ " editors/3d/navigation/warped_mouse_panning " ] = EDITOR_GET ( " editors/3d/navigation/warped_mouse_panning " ) ;
2025-02-09 14:22:24 +01:00
settings [ " editors/3d/freelook/freelook_base_speed " ] = EDITOR_GET ( " editors/3d/freelook/freelook_base_speed " ) ;
2025-03-06 23:07:24 -03:00
settings [ " editors/3d/freelook/freelook_sensitivity " ] = EDITOR_GET ( " editors/3d/freelook/freelook_sensitivity " ) ;
settings [ " editors/3d/navigation_feel/orbit_sensitivity " ] = EDITOR_GET ( " editors/3d/navigation_feel/orbit_sensitivity " ) ;
settings [ " editors/3d/navigation_feel/translation_sensitivity " ] = EDITOR_GET ( " editors/3d/navigation_feel/translation_sensitivity " ) ;
2024-11-18 15:44:38 -03:00
settings [ " editors/3d/selection_box_color " ] = EDITOR_GET ( " editors/3d/selection_box_color " ) ;
settings [ " editors/3d/freelook/freelook_base_speed " ] = EDITOR_GET ( " editors/3d/freelook/freelook_base_speed " ) ;
Array setup_data ;
2024-11-06 04:01:58 +01:00
setup_data . append ( settings ) ;
p_session - > send_message ( " scene:runtime_node_select_setup " , setup_data ) ;
2024-09-18 18:07:18 -03:00
Array type ;
type . append ( node_type ) ;
p_session - > send_message ( " scene:runtime_node_select_set_type " , type ) ;
Array visible ;
visible . append ( selection_visible ) ;
p_session - > send_message ( " scene:runtime_node_select_set_visible " , visible ) ;
Array mode ;
mode . append ( select_mode ) ;
p_session - > send_message ( " scene:runtime_node_select_set_mode " , mode ) ;
2024-11-21 21:31:00 -08:00
Array mute_audio_data ;
mute_audio_data . append ( mute_audio ) ;
p_session - > send_message ( " scene:debug_mute_audio " , mute_audio_data ) ;
2024-09-18 18:07:18 -03:00
2025-04-25 02:36:00 -03:00
Dictionary shortcut_settings ;
shortcut_settings [ " editor/suspend_resume_embedded_project " ] = DebuggerMarshalls : : serialize_key_shortcut ( ED_GET_SHORTCUT ( " editor/suspend_resume_embedded_project " ) ) ;
shortcut_settings [ " editor/next_frame_embedded_project " ] = DebuggerMarshalls : : serialize_key_shortcut ( ED_GET_SHORTCUT ( " editor/next_frame_embedded_project " ) ) ;
p_session - > send_message ( " scene:setup_embedded_shortcuts " , { shortcut_settings } ) ;
2024-09-18 18:07:18 -03:00
emit_signal ( SNAME ( " session_started " ) ) ;
}
void GameViewDebugger : : _session_stopped ( ) {
2025-01-10 14:53:41 -05:00
if ( ! is_feature_enabled ) {
return ;
}
2024-09-18 18:07:18 -03:00
emit_signal ( SNAME ( " session_stopped " ) ) ;
}
void GameViewDebugger : : set_suspend ( bool p_enabled ) {
Array message ;
message . append ( p_enabled ) ;
for ( Ref < EditorDebuggerSession > & I : sessions ) {
if ( I - > is_active ( ) ) {
I - > send_message ( " scene:suspend_changed " , message ) ;
}
}
}
void GameViewDebugger : : next_frame ( ) {
for ( Ref < EditorDebuggerSession > & I : sessions ) {
if ( I - > is_active ( ) ) {
I - > send_message ( " scene:next_frame " , Array ( ) ) ;
}
}
}
2025-06-07 21:47:43 -07:00
void GameViewDebugger : : set_time_scale ( double p_scale ) {
Array message ;
message . append ( p_scale ) ;
for ( Ref < EditorDebuggerSession > & I : sessions ) {
if ( I - > is_active ( ) ) {
I - > send_message ( " scene:speed_changed " , message ) ;
}
}
}
void GameViewDebugger : : reset_time_scale ( ) {
Array message ;
message . append ( 1.0 ) ;
for ( Ref < EditorDebuggerSession > & I : sessions ) {
if ( I - > is_active ( ) ) {
I - > send_message ( " scene:speed_changed " , message ) ;
}
}
}
2024-09-18 18:07:18 -03:00
void GameViewDebugger : : set_node_type ( int p_type ) {
node_type = p_type ;
Array message ;
message . append ( p_type ) ;
for ( Ref < EditorDebuggerSession > & I : sessions ) {
if ( I - > is_active ( ) ) {
I - > send_message ( " scene:runtime_node_select_set_type " , message ) ;
}
}
}
void GameViewDebugger : : set_selection_visible ( bool p_visible ) {
selection_visible = p_visible ;
Array message ;
message . append ( p_visible ) ;
for ( Ref < EditorDebuggerSession > & I : sessions ) {
if ( I - > is_active ( ) ) {
I - > send_message ( " scene:runtime_node_select_set_visible " , message ) ;
}
}
}
void GameViewDebugger : : set_select_mode ( int p_mode ) {
select_mode = p_mode ;
Array message ;
message . append ( p_mode ) ;
for ( Ref < EditorDebuggerSession > & I : sessions ) {
if ( I - > is_active ( ) ) {
I - > send_message ( " scene:runtime_node_select_set_mode " , message ) ;
}
}
}
2024-11-21 21:31:00 -08:00
void GameViewDebugger : : set_debug_mute_audio ( bool p_enabled ) {
mute_audio = p_enabled ;
EditorDebuggerNode : : get_singleton ( ) - > set_debug_mute_audio ( p_enabled ) ;
}
2024-09-18 18:07:18 -03:00
void GameViewDebugger : : set_camera_override ( bool p_enabled ) {
EditorDebuggerNode : : get_singleton ( ) - > set_camera_override ( p_enabled ? camera_override_mode : EditorDebuggerNode : : OVERRIDE_NONE ) ;
}
void GameViewDebugger : : set_camera_manipulate_mode ( EditorDebuggerNode : : CameraOverride p_mode ) {
camera_override_mode = p_mode ;
if ( EditorDebuggerNode : : get_singleton ( ) - > get_camera_override ( ) ! = EditorDebuggerNode : : OVERRIDE_NONE ) {
set_camera_override ( true ) ;
}
}
void GameViewDebugger : : reset_camera_2d_position ( ) {
for ( Ref < EditorDebuggerSession > & I : sessions ) {
if ( I - > is_active ( ) ) {
I - > send_message ( " scene:runtime_node_select_reset_camera_2d " , Array ( ) ) ;
}
}
}
void GameViewDebugger : : reset_camera_3d_position ( ) {
for ( Ref < EditorDebuggerSession > & I : sessions ) {
if ( I - > is_active ( ) ) {
I - > send_message ( " scene:runtime_node_select_reset_camera_3d " , Array ( ) ) ;
}
}
}
void GameViewDebugger : : setup_session ( int p_session_id ) {
Ref < EditorDebuggerSession > session = get_session ( p_session_id ) ;
ERR_FAIL_COND ( session . is_null ( ) ) ;
sessions . append ( session ) ;
session - > connect ( " started " , callable_mp ( this , & GameViewDebugger : : _session_started ) . bind ( session ) ) ;
session - > connect ( " stopped " , callable_mp ( this , & GameViewDebugger : : _session_stopped ) ) ;
}
2025-03-22 10:07:19 -03:00
void GameViewDebugger : : _feature_profile_changed ( ) {
Ref < EditorFeatureProfile > profile = EditorFeatureProfileManager : : get_singleton ( ) - > get_current_profile ( ) ;
is_feature_enabled = profile . is_null ( ) | | ! profile - > is_feature_disabled ( EditorFeatureProfile : : FEATURE_GAME ) ;
}
2024-09-18 18:07:18 -03:00
void GameViewDebugger : : _bind_methods ( ) {
ADD_SIGNAL ( MethodInfo ( " session_started " ) ) ;
ADD_SIGNAL ( MethodInfo ( " session_stopped " ) ) ;
}
2025-05-29 08:07:49 +03:00
bool GameViewDebugger : : add_screenshot_callback ( const Callable & p_callaback , const Rect2i & p_rect ) {
bool found = false ;
for ( Ref < EditorDebuggerSession > & I : sessions ) {
if ( I - > is_active ( ) ) {
ScreenshotCB sd ;
sd . cb = p_callaback ;
sd . rect = p_rect ;
screenshot_callbacks [ scr_rq_id ] = sd ;
Array arr ;
arr . append ( scr_rq_id ) ;
I - > send_message ( " scene:rq_screenshot " , arr ) ;
scr_rq_id + + ;
found = true ;
}
}
return found ;
}
bool GameViewDebugger : : _msg_get_screenshot ( const Array & p_args ) {
ERR_FAIL_COND_V_MSG ( p_args . size ( ) ! = 4 , false , " get_screenshot: invalid number of arguments " ) ;
int64_t id = p_args [ 0 ] ;
int64_t w = p_args [ 1 ] ;
int64_t h = p_args [ 2 ] ;
const String & path = p_args [ 3 ] ;
if ( screenshot_callbacks . has ( id ) ) {
if ( screenshot_callbacks [ id ] . cb . is_valid ( ) ) {
screenshot_callbacks [ id ] . cb . call ( w , h , path , screenshot_callbacks [ id ] . rect ) ;
}
screenshot_callbacks . erase ( id ) ;
}
return true ;
}
bool GameViewDebugger : : capture ( const String & p_message , const Array & p_data , int p_session ) {
Ref < EditorDebuggerSession > session = get_session ( p_session ) ;
ERR_FAIL_COND_V ( session . is_null ( ) , true ) ;
if ( p_message = = " game_view:get_screenshot " ) {
return _msg_get_screenshot ( p_data ) ;
} else {
// Any other messages with this prefix should be ignored.
WARN_PRINT ( " GameViewDebugger unknown message: " + p_message ) ;
return false ;
}
return true ;
}
bool GameViewDebugger : : has_capture ( const String & p_capture ) const {
return p_capture = = " game_view " ;
}
2025-03-22 10:07:19 -03:00
GameViewDebugger : : GameViewDebugger ( ) {
EditorFeatureProfileManager : : get_singleton ( ) - > connect ( " current_feature_profile_changed " , callable_mp ( this , & GameViewDebugger : : _feature_profile_changed ) ) ;
2025-06-10 22:15:02 -07:00
ED_SHORTCUT ( " editor/suspend_resume_embedded_project " , TTRC ( " Suspend/Resume Embedded Project " ) , Key : : F9 ) ;
ED_SHORTCUT_OVERRIDE ( " editor/suspend_resume_embedded_project " , " macos " , KeyModifierMask : : META | KeyModifierMask : : SHIFT | Key : : B ) ;
ED_SHORTCUT ( " editor/next_frame_embedded_project " , TTRC ( " Next Frame " ) , Key : : F10 ) ;
2025-03-22 10:07:19 -03:00
}
2024-09-18 18:07:18 -03:00
///////
void GameView : : _sessions_changed ( ) {
// The debugger session's `session_started/stopped` signal can be unreliable, so count it manually.
active_sessions = 0 ;
Array sessions = debugger - > get_sessions ( ) ;
for ( int i = 0 ; i < sessions . size ( ) ; i + + ) {
if ( Object : : cast_to < EditorDebuggerSession > ( sessions [ i ] ) - > is_active ( ) ) {
active_sessions + + ;
}
}
_update_debugger_buttons ( ) ;
2025-01-19 15:45:59 -05:00
2025-04-29 07:01:27 +10:00
# ifdef MACOS_ENABLED
if ( ! embedded_script_debugger | | ! embedded_script_debugger - > is_session_active ( ) | | embedded_script_debugger - > get_remote_pid ( ) ! = embedded_process - > get_embedded_pid ( ) ) {
_attach_script_debugger ( ) ;
}
# else
2025-01-19 15:45:59 -05:00
if ( embedded_process - > is_embedding_completed ( ) ) {
if ( ! embedded_script_debugger | | ! embedded_script_debugger - > is_session_active ( ) | | embedded_script_debugger - > get_remote_pid ( ) ! = embedded_process - > get_embedded_pid ( ) ) {
_attach_script_debugger ( ) ;
}
}
2025-04-29 07:01:27 +10:00
# endif
2024-09-18 18:07:18 -03:00
}
2024-09-25 16:57:23 -04:00
void GameView : : _instance_starting_static ( int p_idx , List < String > & r_arguments ) {
ERR_FAIL_NULL ( singleton ) ;
singleton - > _instance_starting ( p_idx , r_arguments ) ;
}
void GameView : : _instance_starting ( int p_idx , List < String > & r_arguments ) {
2025-01-10 14:53:41 -05:00
if ( ! is_feature_enabled ) {
return ;
}
2025-03-22 10:07:19 -03:00
2025-01-31 07:22:24 -05:00
if ( p_idx = = 0 & & embed_on_play & & make_floating_on_play & & window_wrapper - > is_window_available ( ) & & ! window_wrapper - > get_window_enabled ( ) & & _get_embed_available ( ) = = EMBED_AVAILABLE ) {
2025-01-19 15:45:59 -05:00
// Set the Floating Window default title. Always considered in DEBUG mode, same as in Window::set_title.
String appname = GLOBAL_GET ( " application/config/name " ) ;
appname = vformat ( " %s (DEBUG) " , TranslationServer : : get_singleton ( ) - > translate ( appname ) ) ;
window_wrapper - > set_window_title ( appname ) ;
2025-01-20 18:33:12 -05:00
_show_update_window_wrapper ( ) ;
2025-02-01 21:15:54 -05:00
embedded_process - > grab_focus ( ) ;
2024-09-25 16:57:23 -04:00
}
_update_arguments_for_instance ( p_idx , r_arguments ) ;
}
2025-05-29 08:07:49 +03:00
bool GameView : : _instance_rq_screenshot_static ( const Callable & p_callback ) {
ERR_FAIL_NULL_V ( singleton , false ) ;
return singleton - > _instance_rq_screenshot ( p_callback ) ;
}
bool GameView : : _instance_rq_screenshot ( const Callable & p_callback ) {
if ( debugger . is_null ( ) | | window_wrapper - > get_window_enabled ( ) | | ! embedded_process | | ! embedded_process - > is_embedding_completed ( ) ) {
return false ;
}
Rect2 r = embedded_process - > get_adjusted_embedded_window_rect ( embedded_process - > get_rect ( ) ) ;
r . position + = embedded_process - > get_global_position ( ) ;
# ifndef MACOS_ENABLED
r . position - = embedded_process - > get_window ( ) - > get_position ( ) ;
# endif
return debugger - > add_screenshot_callback ( p_callback , r ) ;
}
2025-01-20 18:33:12 -05:00
void GameView : : _show_update_window_wrapper ( ) {
EditorRun : : WindowPlacement placement = EditorRun : : get_window_placement ( ) ;
Point2 position = floating_window_rect . position ;
Size2i size = floating_window_rect . size ;
int screen = floating_window_screen ;
// Obtain the size around the embedded process control. Usually, the difference between the game view's get_size
// and the embedded control should work. However, when the control is hidden and has never been displayed,
// the size of the embedded control is not calculated.
Size2 old_min_size = embedded_process - > get_custom_minimum_size ( ) ;
embedded_process - > set_custom_minimum_size ( Size2i ( ) ) ;
2025-02-11 21:41:03 -05:00
Size2 embedded_process_min_size = get_minimum_size ( ) ;
Size2 wrapped_margins_size = window_wrapper - > get_margins_size ( ) ;
Size2 wrapped_min_size = window_wrapper - > get_minimum_size ( ) ;
Point2 offset_embedded_process = embedded_process - > get_global_position ( ) - get_global_position ( ) ;
// On the first startup, the global position of the embedded process control is invalid because it was
2025-03-01 10:31:31 -05:00
// never displayed. We will calculate it manually using the minimum size of the window.
2025-02-11 21:41:03 -05:00
if ( offset_embedded_process = = Point2 ( ) ) {
offset_embedded_process . y = wrapped_min_size . y ;
}
offset_embedded_process . x + = embedded_process - > get_margin_size ( SIDE_LEFT ) ;
offset_embedded_process . y + = embedded_process - > get_margin_size ( SIDE_TOP ) ;
offset_embedded_process + = window_wrapper - > get_margins_top_left ( ) ;
2025-01-20 18:33:12 -05:00
embedded_process - > set_custom_minimum_size ( old_min_size ) ;
2025-02-11 21:41:03 -05:00
Point2 size_diff_embedded_process = Point2 ( 0 , embedded_process_min_size . y ) + embedded_process - > get_margins_size ( ) ;
2025-01-20 18:33:12 -05:00
if ( placement . position ! = Point2i ( INT_MAX , INT_MAX ) ) {
position = placement . position - offset_embedded_process ;
screen = placement . screen ;
}
if ( placement . size ! = Size2i ( ) ) {
size = placement . size + size_diff_embedded_process + wrapped_margins_size ;
}
window_wrapper - > restore_window_from_saved_position ( Rect2 ( position , size ) , screen , Rect2i ( ) ) ;
}
2024-09-25 16:57:23 -04:00
void GameView : : _play_pressed ( ) {
2025-01-10 14:53:41 -05:00
if ( ! is_feature_enabled ) {
return ;
}
2024-09-25 16:57:23 -04:00
OS : : ProcessID current_process_id = EditorRunBar : : get_singleton ( ) - > get_current_process ( ) ;
if ( current_process_id = = 0 ) {
return ;
}
if ( ! window_wrapper - > get_window_enabled ( ) ) {
screen_index_before_start = EditorNode : : get_singleton ( ) - > get_editor_main_screen ( ) - > get_selected_index ( ) ;
}
2025-01-20 18:33:12 -05:00
if ( embed_on_play & & _get_embed_available ( ) = = EMBED_AVAILABLE ) {
2024-09-25 16:57:23 -04:00
// It's important to disable the low power mode when unfocused because otherwise
// the button in the editor are not responsive and if the user moves the mouse quickly,
// the mouse clicks are not registered.
EditorNode : : get_singleton ( ) - > set_unfocused_low_processor_usage_mode_enabled ( false ) ;
_update_embed_window_size ( ) ;
if ( ! window_wrapper - > get_window_enabled ( ) ) {
EditorNode : : get_singleton ( ) - > get_editor_main_screen ( ) - > select ( EditorMainScreen : : EDITOR_GAME ) ;
2025-02-17 18:14:41 -05:00
// Reset the normal size of the bottom panel when fully expanded.
EditorNode : : get_singleton ( ) - > get_bottom_panel ( ) - > set_expanded ( false ) ;
2024-09-25 16:57:23 -04:00
embedded_process - > grab_focus ( ) ;
}
embedded_process - > embed_process ( current_process_id ) ;
_update_ui ( ) ;
}
}
void GameView : : _stop_pressed ( ) {
2025-01-10 14:53:41 -05:00
if ( ! is_feature_enabled ) {
return ;
}
2025-01-19 15:45:59 -05:00
_detach_script_debugger ( ) ;
2025-01-24 20:44:47 -05:00
paused = false ;
2025-01-19 15:45:59 -05:00
2024-09-25 16:57:23 -04:00
EditorNode : : get_singleton ( ) - > set_unfocused_low_processor_usage_mode_enabled ( true ) ;
embedded_process - > reset ( ) ;
_update_ui ( ) ;
if ( window_wrapper - > get_window_enabled ( ) ) {
window_wrapper - > set_window_enabled ( false ) ;
}
if ( screen_index_before_start > = 0 & & EditorNode : : get_singleton ( ) - > get_editor_main_screen ( ) - > get_selected_index ( ) = = EditorMainScreen : : EDITOR_GAME ) {
// We go back to the screen where the user was before starting the game.
EditorNode : : get_singleton ( ) - > get_editor_main_screen ( ) - > select ( screen_index_before_start ) ;
}
screen_index_before_start = - 1 ;
}
void GameView : : _embedding_completed ( ) {
2025-04-29 07:01:27 +10:00
# ifndef MACOS_ENABLED
2025-01-19 15:45:59 -05:00
_attach_script_debugger ( ) ;
2025-04-29 07:01:27 +10:00
# endif
2024-09-25 16:57:23 -04:00
_update_ui ( ) ;
2025-03-06 20:01:46 -03:00
if ( make_floating_on_play ) {
get_window ( ) - > set_flag ( Window : : FLAG_ALWAYS_ON_TOP , bool ( GLOBAL_GET ( " display/window/size/always_on_top " ) ) ) ;
}
2024-09-25 16:57:23 -04:00
}
void GameView : : _embedding_failed ( ) {
2025-05-30 00:10:53 +02:00
state_label - > set_text ( TTRC ( " Connection impossible to the game process. " ) ) ;
2024-09-25 16:57:23 -04:00
}
void GameView : : _embedded_process_updated ( ) {
const Rect2i game_rect = embedded_process - > get_screen_embedded_window_rect ( ) ;
game_size_label - > set_text ( vformat ( " %dx%d " , game_rect . size . x , game_rect . size . y ) ) ;
}
2024-12-29 18:16:19 -05:00
void GameView : : _embedded_process_focused ( ) {
if ( embed_on_play & & ! window_wrapper - > get_window_enabled ( ) ) {
EditorNode : : get_singleton ( ) - > get_editor_main_screen ( ) - > select ( EditorMainScreen : : EDITOR_GAME ) ;
}
}
2025-01-20 18:33:12 -05:00
void GameView : : _editor_or_project_settings_changed ( ) {
2024-09-25 16:57:23 -04:00
// Update the window size and aspect ratio.
_update_embed_window_size ( ) ;
2025-01-20 18:33:12 -05:00
if ( window_wrapper - > get_window_enabled ( ) ) {
_show_update_window_wrapper ( ) ;
if ( embedded_process - > is_embedding_completed ( ) ) {
embedded_process - > queue_update_embedded_process ( ) ;
}
}
_update_ui ( ) ;
2024-09-25 16:57:23 -04:00
}
2024-09-18 18:07:18 -03:00
void GameView : : _update_debugger_buttons ( ) {
bool empty = active_sessions = = 0 ;
suspend_button - > set_disabled ( empty ) ;
camera_override_button - > set_disabled ( empty ) ;
2025-06-07 21:47:43 -07:00
speed_state_button - > set_disabled ( empty ) ;
reset_speed_button - > set_disabled ( empty ) ;
2024-09-18 18:07:18 -03:00
PopupMenu * menu = camera_override_menu - > get_popup ( ) ;
bool disable_camera_reset = empty | | ! camera_override_button - > is_pressed ( ) | | ! menu - > is_item_checked ( menu - > get_item_index ( CAMERA_MODE_INGAME ) ) ;
menu - > set_item_disabled ( CAMERA_RESET_2D , disable_camera_reset ) ;
menu - > set_item_disabled ( CAMERA_RESET_3D , disable_camera_reset ) ;
if ( empty ) {
suspend_button - > set_pressed ( false ) ;
camera_override_button - > set_pressed ( false ) ;
}
next_frame_button - > set_disabled ( ! suspend_button - > is_pressed ( ) ) ;
2025-06-07 21:47:43 -07:00
_reset_time_scales ( ) ;
2024-09-18 18:07:18 -03:00
}
2025-04-25 02:36:00 -03:00
void GameView : : _handle_shortcut_requested ( int p_embed_action ) {
switch ( p_embed_action ) {
case ScriptEditorDebugger : : EMBED_SUSPEND_TOGGLE : {
_toggle_suspend_button ( ) ;
} break ;
case ScriptEditorDebugger : : EMBED_NEXT_FRAME : {
debugger - > next_frame ( ) ;
} break ;
}
}
void GameView : : _toggle_suspend_button ( ) {
const bool new_pressed = ! suspend_button - > is_pressed ( ) ;
suspend_button - > set_pressed ( new_pressed ) ;
_suspend_button_toggled ( new_pressed ) ;
}
2024-09-18 18:07:18 -03:00
void GameView : : _suspend_button_toggled ( bool p_pressed ) {
_update_debugger_buttons ( ) ;
debugger - > set_suspend ( p_pressed ) ;
}
void GameView : : _node_type_pressed ( int p_option ) {
RuntimeNodeSelect : : NodeType type = ( RuntimeNodeSelect : : NodeType ) p_option ;
for ( int i = 0 ; i < RuntimeNodeSelect : : NODE_TYPE_MAX ; i + + ) {
node_type_button [ i ] - > set_pressed_no_signal ( i = = type ) ;
}
_update_debugger_buttons ( ) ;
debugger - > set_node_type ( type ) ;
}
void GameView : : _select_mode_pressed ( int p_option ) {
RuntimeNodeSelect : : SelectMode mode = ( RuntimeNodeSelect : : SelectMode ) p_option ;
2025-03-22 10:07:19 -03:00
if ( ! select_mode_button [ mode ] - > is_visible ( ) ) {
return ;
}
2024-09-18 18:07:18 -03:00
for ( int i = 0 ; i < RuntimeNodeSelect : : SELECT_MODE_MAX ; i + + ) {
select_mode_button [ i ] - > set_pressed_no_signal ( i = = mode ) ;
}
debugger - > set_select_mode ( mode ) ;
2024-11-18 15:44:38 -03:00
EditorSettings : : get_singleton ( ) - > set_project_metadata ( " game_view " , " select_mode " , mode ) ;
2024-09-18 18:07:18 -03:00
}
2024-09-25 16:57:23 -04:00
void GameView : : _embed_options_menu_menu_id_pressed ( int p_id ) {
switch ( p_id ) {
case EMBED_RUN_GAME_EMBEDDED : {
embed_on_play = ! embed_on_play ;
2025-01-08 12:17:35 +02:00
int game_mode = EDITOR_GET ( " run/window_placement/game_embed_mode " ) ;
if ( game_mode = = 0 ) { // Save only if not overridden by editor.
EditorSettings : : get_singleton ( ) - > set_project_metadata ( " game_view " , " embed_on_play " , embed_on_play ) ;
}
2024-09-25 16:57:23 -04:00
} break ;
case EMBED_MAKE_FLOATING_ON_PLAY : {
make_floating_on_play = ! make_floating_on_play ;
2025-01-08 12:17:35 +02:00
int game_mode = EDITOR_GET ( " run/window_placement/game_embed_mode " ) ;
if ( game_mode = = 0 ) { // Save only if not overridden by editor.
EditorSettings : : get_singleton ( ) - > set_project_metadata ( " game_view " , " make_floating_on_play " , make_floating_on_play ) ;
}
2024-09-25 16:57:23 -04:00
} break ;
2025-03-21 07:00:49 +00:00
case SIZE_MODE_FIXED :
case SIZE_MODE_KEEP_ASPECT :
case SIZE_MODE_STRETCH : {
embed_size_mode = ( EmbedSizeMode ) p_id ;
EditorSettings : : get_singleton ( ) - > set_project_metadata ( " game_view " , " embed_size_mode " , p_id ) ;
_update_embed_window_size ( ) ;
} break ;
2024-09-25 16:57:23 -04:00
}
_update_embed_menu_options ( ) ;
2025-01-20 18:33:12 -05:00
_update_ui ( ) ;
2024-09-25 16:57:23 -04:00
}
2025-06-07 21:47:43 -07:00
void GameView : : _reset_time_scales ( ) {
if ( ! is_visible_in_tree ( ) ) {
return ;
}
time_scale_index = DEFAULT_TIME_SCALE_INDEX ;
debugger - > reset_time_scale ( ) ;
_update_speed_buttons ( ) ;
}
void GameView : : _speed_state_menu_pressed ( int p_id ) {
time_scale_index = p_id ;
debugger - > set_time_scale ( time_scale_range [ time_scale_index ] ) ;
_update_speed_buttons ( ) ;
}
void GameView : : _update_speed_buttons ( ) {
bool disabled = time_scale_index = = DEFAULT_TIME_SCALE_INDEX ;
reset_speed_button - > set_disabled ( disabled ) ;
speed_state_button - > set_text ( vformat ( U " %s× " , time_scale_label [ time_scale_index ] ) ) ;
_update_speed_state_color ( ) ;
}
void GameView : : _update_speed_state_color ( ) {
Color text_color ;
if ( time_scale_index = = DEFAULT_TIME_SCALE_INDEX ) {
text_color = get_theme_color ( SceneStringName ( font_color ) , EditorStringName ( Editor ) ) ;
} else if ( time_scale_index > DEFAULT_TIME_SCALE_INDEX ) {
text_color = get_theme_color ( SNAME ( " success_color " ) , EditorStringName ( Editor ) ) ;
} else if ( time_scale_index < DEFAULT_TIME_SCALE_INDEX ) {
text_color = get_theme_color ( SNAME ( " warning_color " ) , EditorStringName ( Editor ) ) ;
}
speed_state_button - > add_theme_color_override ( SceneStringName ( font_color ) , text_color ) ;
}
void GameView : : _update_speed_state_size ( ) {
if ( ! speed_state_button ) {
return ;
}
float min_size = 0 ;
for ( const String lbl : time_scale_label ) {
min_size = MAX ( speed_state_button - > get_minimum_size_for_text_and_icon ( vformat ( U " %s× " , lbl ) , Ref < Texture2D > ( ) ) . x , min_size ) ;
}
speed_state_button - > set_custom_minimum_size ( Vector2 ( min_size , 0 ) ) ;
}
2025-01-20 18:33:12 -05:00
GameView : : EmbedAvailability GameView : : _get_embed_available ( ) {
if ( ! DisplayServer : : get_singleton ( ) - > has_feature ( DisplayServer : : FEATURE_WINDOW_EMBEDDING ) ) {
return EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED ;
}
2025-01-31 07:22:24 -05:00
if ( get_tree ( ) - > get_root ( ) - > is_embedding_subwindows ( ) ) {
2025-01-22 16:34:01 -05:00
return EMBED_NOT_AVAILABLE_SINGLE_WINDOW_MODE ;
}
2025-02-13 18:42:42 -05:00
String display_driver = GLOBAL_GET ( " display/display_server/driver " ) ;
2025-11-15 23:38:41 +01:00
if ( display_driver = = " headless " ) {
2025-02-13 18:42:42 -05:00
return EMBED_NOT_AVAILABLE_PROJECT_DISPLAY_DRIVER ;
}
2025-01-20 18:33:12 -05:00
EditorRun : : WindowPlacement placement = EditorRun : : get_window_placement ( ) ;
if ( placement . force_fullscreen ) {
return EMBED_NOT_AVAILABLE_FULLSCREEN ;
}
if ( placement . force_maximized ) {
return EMBED_NOT_AVAILABLE_MAXIMIZED ;
}
DisplayServer : : WindowMode window_mode = ( DisplayServer : : WindowMode ) ( GLOBAL_GET ( " display/window/size/mode " ) . operator int ( ) ) ;
if ( window_mode = = DisplayServer : : WindowMode : : WINDOW_MODE_MINIMIZED ) {
return EMBED_NOT_AVAILABLE_MINIMIZED ;
}
if ( window_mode = = DisplayServer : : WindowMode : : WINDOW_MODE_MAXIMIZED ) {
return EMBED_NOT_AVAILABLE_MAXIMIZED ;
}
if ( window_mode = = DisplayServer : : WindowMode : : WINDOW_MODE_FULLSCREEN | | window_mode = = DisplayServer : : WindowMode : : WINDOW_MODE_EXCLUSIVE_FULLSCREEN ) {
return EMBED_NOT_AVAILABLE_FULLSCREEN ;
}
return EMBED_AVAILABLE ;
2024-09-25 16:57:23 -04:00
}
void GameView : : _update_ui ( ) {
bool show_game_size = false ;
2025-01-20 18:33:12 -05:00
EmbedAvailability available = _get_embed_available ( ) ;
switch ( available ) {
case EMBED_AVAILABLE :
if ( embedded_process - > is_embedding_completed ( ) ) {
state_label - > set_text ( " " ) ;
show_game_size = true ;
} else if ( embedded_process - > is_embedding_in_progress ( ) ) {
2025-05-30 00:10:53 +02:00
state_label - > set_text ( TTRC ( " Game starting... " ) ) ;
2025-01-20 18:33:12 -05:00
} else if ( EditorRunBar : : get_singleton ( ) - > is_playing ( ) ) {
2025-05-30 00:10:53 +02:00
state_label - > set_text ( TTRC ( " Game running not embedded. " ) ) ;
2025-01-20 18:33:12 -05:00
} else if ( embed_on_play ) {
2025-05-30 00:10:53 +02:00
state_label - > set_text ( TTRC ( " Press play to start the game. " ) ) ;
2025-01-20 18:33:12 -05:00
} else {
2025-05-30 00:10:53 +02:00
state_label - > set_text ( TTRC ( " Embedding is disabled. " ) ) ;
2025-01-20 18:33:12 -05:00
}
break ;
case EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED :
2025-11-15 23:38:41 +01:00
state_label - > set_text ( TTRC ( " Game embedding not available on your OS. " ) ) ;
2025-02-13 18:42:42 -05:00
break ;
case EMBED_NOT_AVAILABLE_PROJECT_DISPLAY_DRIVER :
state_label - > set_text ( vformat ( TTR ( " Game embedding not available for the Display Server: '%s'. \n Display Server can be modified in the Project Settings (Display > Display Server > Driver). " ) , GLOBAL_GET ( " display/display_server/driver " ) ) ) ;
2025-01-20 18:33:12 -05:00
break ;
case EMBED_NOT_AVAILABLE_MINIMIZED :
2025-02-10 18:45:30 +01:00
state_label - > set_text ( TTR ( " Game embedding not available when the game starts minimized. " ) + " \n " + TTR ( " Consider overriding the window mode project setting with the editor feature tag to Windowed to use game embedding while leaving the exported project intact. " ) ) ;
2025-01-20 18:33:12 -05:00
break ;
case EMBED_NOT_AVAILABLE_MAXIMIZED :
2025-02-10 18:45:30 +01:00
state_label - > set_text ( TTR ( " Game embedding not available when the game starts maximized. " ) + " \n " + TTR ( " Consider overriding the window mode project setting with the editor feature tag to Windowed to use game embedding while leaving the exported project intact. " ) ) ;
2025-01-20 18:33:12 -05:00
break ;
case EMBED_NOT_AVAILABLE_FULLSCREEN :
2025-02-10 18:45:30 +01:00
state_label - > set_text ( TTR ( " Game embedding not available when the game starts in fullscreen. " ) + " \n " + TTR ( " Consider overriding the window mode project setting with the editor feature tag to Windowed to use game embedding while leaving the exported project intact. " ) ) ;
2025-01-20 18:33:12 -05:00
break ;
2025-01-22 16:34:01 -05:00
case EMBED_NOT_AVAILABLE_SINGLE_WINDOW_MODE :
2025-05-30 00:10:53 +02:00
state_label - > set_text ( TTRC ( " Game embedding not available in single window mode. " ) ) ;
2025-01-22 16:34:01 -05:00
break ;
2025-01-20 18:33:12 -05:00
}
if ( available = = EMBED_AVAILABLE ) {
if ( state_label - > has_theme_color_override ( SceneStringName ( font_color ) ) ) {
state_label - > remove_theme_color_override ( SceneStringName ( font_color ) ) ;
}
2024-09-25 16:57:23 -04:00
} else {
2025-01-20 18:33:12 -05:00
state_label - > add_theme_color_override ( SceneStringName ( font_color ) , state_label - > get_theme_color ( SNAME ( " warning_color " ) , EditorStringName ( Editor ) ) ) ;
2024-09-25 16:57:23 -04:00
}
game_size_label - > set_visible ( show_game_size ) ;
}
void GameView : : _update_embed_menu_options ( ) {
2025-01-31 07:22:24 -05:00
bool is_multi_window = window_wrapper - > is_window_available ( ) ;
2024-09-25 16:57:23 -04:00
PopupMenu * menu = embed_options_menu - > get_popup ( ) ;
menu - > set_item_checked ( menu - > get_item_index ( EMBED_RUN_GAME_EMBEDDED ) , embed_on_play ) ;
2025-01-31 07:22:24 -05:00
menu - > set_item_checked ( menu - > get_item_index ( EMBED_MAKE_FLOATING_ON_PLAY ) , make_floating_on_play & & is_multi_window ) ;
2024-09-25 16:57:23 -04:00
2025-03-21 07:00:49 +00:00
menu - > set_item_checked ( menu - > get_item_index ( SIZE_MODE_FIXED ) , embed_size_mode = = SIZE_MODE_FIXED ) ;
menu - > set_item_checked ( menu - > get_item_index ( SIZE_MODE_KEEP_ASPECT ) , embed_size_mode = = SIZE_MODE_KEEP_ASPECT ) ;
menu - > set_item_checked ( menu - > get_item_index ( SIZE_MODE_STRETCH ) , embed_size_mode = = SIZE_MODE_STRETCH ) ;
2025-01-20 18:33:12 -05:00
2025-03-21 07:00:49 +00:00
menu - > set_item_disabled ( menu - > get_item_index ( EMBED_MAKE_FLOATING_ON_PLAY ) , ! embed_on_play | | ! is_multi_window ) ;
2024-09-25 16:57:23 -04:00
}
void GameView : : _update_embed_window_size ( ) {
2025-01-24 20:44:47 -05:00
if ( paused ) {
// When paused, Godot does not re-render. As a result, resizing the game window to a larger size
// causes artifacts and flickering. However, resizing to a smaller size seems fine.
// To prevent artifacts and flickering, we will force the game window to maintain its size.
// Using the same technique as SIZE_MODE_FIXED, the embedded process control will
// prevent resizing the game to a larger size while maintaining the aspect ratio.
embedded_process - > set_window_size ( size_paused ) ;
embedded_process - > set_keep_aspect ( false ) ;
2025-01-20 18:33:12 -05:00
} else {
2025-01-24 20:44:47 -05:00
if ( embed_size_mode = = SIZE_MODE_FIXED | | embed_size_mode = = SIZE_MODE_KEEP_ASPECT ) {
// The embedded process control will need the desired window size.
EditorRun : : WindowPlacement placement = EditorRun : : get_window_placement ( ) ;
embedded_process - > set_window_size ( placement . size ) ;
} else {
// Stretch... No need for the window size.
embedded_process - > set_window_size ( Size2i ( ) ) ;
}
embedded_process - > set_keep_aspect ( embed_size_mode = = SIZE_MODE_KEEP_ASPECT ) ;
2024-09-25 16:57:23 -04:00
}
}
2024-09-18 18:07:18 -03:00
void GameView : : _hide_selection_toggled ( bool p_pressed ) {
hide_selection - > set_button_icon ( get_editor_theme_icon ( p_pressed ? SNAME ( " GuiVisibilityHidden " ) : SNAME ( " GuiVisibilityVisible " ) ) ) ;
debugger - > set_selection_visible ( ! p_pressed ) ;
2024-11-18 15:44:38 -03:00
EditorSettings : : get_singleton ( ) - > set_project_metadata ( " game_view " , " hide_selection " , p_pressed ) ;
2024-09-18 18:07:18 -03:00
}
2024-11-21 21:31:00 -08:00
void GameView : : _debug_mute_audio_button_pressed ( ) {
debug_mute_audio = ! debug_mute_audio ;
debug_mute_audio_button - > set_button_icon ( get_editor_theme_icon ( debug_mute_audio ? SNAME ( " AudioMute " ) : SNAME ( " AudioStreamPlayer " ) ) ) ;
debug_mute_audio_button - > set_tooltip_text ( debug_mute_audio ? TTRC ( " Unmute game audio. " ) : TTRC ( " Mute game audio. " ) ) ;
debugger - > set_debug_mute_audio ( debug_mute_audio ) ;
}
2024-09-18 18:07:18 -03:00
void GameView : : _camera_override_button_toggled ( bool p_pressed ) {
_update_debugger_buttons ( ) ;
debugger - > set_camera_override ( p_pressed ) ;
}
void GameView : : _camera_override_menu_id_pressed ( int p_id ) {
PopupMenu * menu = camera_override_menu - > get_popup ( ) ;
if ( p_id ! = CAMERA_RESET_2D & & p_id ! = CAMERA_RESET_3D ) {
for ( int i = 0 ; i < menu - > get_item_count ( ) ; i + + ) {
menu - > set_item_checked ( i , false ) ;
}
}
switch ( p_id ) {
case CAMERA_RESET_2D : {
debugger - > reset_camera_2d_position ( ) ;
} break ;
case CAMERA_RESET_3D : {
debugger - > reset_camera_3d_position ( ) ;
} break ;
case CAMERA_MODE_INGAME : {
debugger - > set_camera_manipulate_mode ( EditorDebuggerNode : : OVERRIDE_INGAME ) ;
menu - > set_item_checked ( menu - > get_item_index ( p_id ) , true ) ;
_update_debugger_buttons ( ) ;
2024-11-18 15:44:38 -03:00
EditorSettings : : get_singleton ( ) - > set_project_metadata ( " game_view " , " camera_override_mode " , p_id ) ;
2024-09-18 18:07:18 -03:00
} break ;
case CAMERA_MODE_EDITORS : {
debugger - > set_camera_manipulate_mode ( EditorDebuggerNode : : OVERRIDE_EDITORS ) ;
menu - > set_item_checked ( menu - > get_item_index ( p_id ) , true ) ;
_update_debugger_buttons ( ) ;
2024-11-18 15:44:38 -03:00
EditorSettings : : get_singleton ( ) - > set_project_metadata ( " game_view " , " camera_override_mode " , p_id ) ;
2024-09-18 18:07:18 -03:00
} break ;
}
}
void GameView : : _notification ( int p_what ) {
switch ( p_what ) {
2025-05-30 00:10:53 +02:00
case NOTIFICATION_TRANSLATION_CHANGED : {
select_mode_button [ RuntimeNodeSelect : : SELECT_MODE_SINGLE ] - > set_tooltip_text ( keycode_get_string ( ( Key ) KeyModifierMask : : CMD_OR_CTRL ) + TTR ( " Alt+RMB: Show list of all nodes at position clicked. " ) ) ;
_update_ui ( ) ;
} break ;
2025-06-07 21:47:43 -07:00
case NOTIFICATION_POST_ENTER_TREE : {
_update_speed_state_size ( ) ;
} break ;
2024-09-18 18:07:18 -03:00
case NOTIFICATION_THEME_CHANGED : {
2025-08-30 07:12:34 +03:00
suspend_button - > set_button_icon ( get_editor_theme_icon ( SNAME ( " Suspend " ) ) ) ;
2024-09-18 18:07:18 -03:00
next_frame_button - > set_button_icon ( get_editor_theme_icon ( SNAME ( " NextFrame " ) ) ) ;
2025-06-07 21:47:43 -07:00
reset_speed_button - > set_button_icon ( get_editor_theme_icon ( SNAME ( " Reload " ) ) ) ;
2024-09-18 18:07:18 -03:00
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_NONE ] - > set_button_icon ( get_editor_theme_icon ( SNAME ( " InputEventJoypadMotion " ) ) ) ;
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_2D ] - > set_button_icon ( get_editor_theme_icon ( SNAME ( " 2DNodes " ) ) ) ;
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_3D ] - > set_button_icon ( get_editor_theme_icon ( SNAME ( " Node3D " ) ) ) ;
select_mode_button [ RuntimeNodeSelect : : SELECT_MODE_SINGLE ] - > set_button_icon ( get_editor_theme_icon ( SNAME ( " ToolSelect " ) ) ) ;
select_mode_button [ RuntimeNodeSelect : : SELECT_MODE_LIST ] - > set_button_icon ( get_editor_theme_icon ( SNAME ( " ListSelect " ) ) ) ;
hide_selection - > set_button_icon ( get_editor_theme_icon ( hide_selection - > is_pressed ( ) ? SNAME ( " GuiVisibilityHidden " ) : SNAME ( " GuiVisibilityVisible " ) ) ) ;
2025-03-21 07:00:49 +00:00
embed_options_menu - > set_button_icon ( get_editor_theme_icon ( SNAME ( " KeepAspect " ) ) ) ;
2024-09-18 18:07:18 -03:00
2024-11-21 21:31:00 -08:00
debug_mute_audio_button - > set_button_icon ( get_editor_theme_icon ( debug_mute_audio ? SNAME ( " AudioMute " ) : SNAME ( " AudioStreamPlayer " ) ) ) ;
2024-09-18 18:07:18 -03:00
camera_override_button - > set_button_icon ( get_editor_theme_icon ( SNAME ( " Camera " ) ) ) ;
camera_override_menu - > set_button_icon ( get_editor_theme_icon ( SNAME ( " GuiTabMenuHl " ) ) ) ;
2025-06-07 21:47:43 -07:00
_update_speed_state_size ( ) ;
_update_speed_state_color ( ) ;
2024-09-18 18:07:18 -03:00
} break ;
2024-09-25 16:57:23 -04:00
case NOTIFICATION_READY : {
if ( DisplayServer : : get_singleton ( ) - > has_feature ( DisplayServer : : FEATURE_WINDOW_EMBEDDING ) ) {
// Embedding available.
2025-01-08 12:17:35 +02:00
int game_mode = EDITOR_GET ( " run/window_placement/game_embed_mode " ) ;
switch ( game_mode ) {
2025-01-07 21:31:53 -08:00
case - 1 : { // Disabled.
embed_on_play = false ;
make_floating_on_play = false ;
} break ;
2025-01-08 12:17:35 +02:00
case 1 : { // Embed.
embed_on_play = true ;
make_floating_on_play = false ;
} break ;
case 2 : { // Floating.
embed_on_play = true ;
make_floating_on_play = true ;
} break ;
default : {
embed_on_play = EditorSettings : : get_singleton ( ) - > get_project_metadata ( " game_view " , " embed_on_play " , true ) ;
make_floating_on_play = EditorSettings : : get_singleton ( ) - > get_project_metadata ( " game_view " , " make_floating_on_play " , true ) ;
} break ;
}
2025-01-20 18:33:12 -05:00
embed_size_mode = ( EmbedSizeMode ) ( int ) EditorSettings : : get_singleton ( ) - > get_project_metadata ( " game_view " , " embed_size_mode " , SIZE_MODE_FIXED ) ;
2024-09-25 16:57:23 -04:00
_update_embed_menu_options ( ) ;
EditorRunBar : : get_singleton ( ) - > connect ( " play_pressed " , callable_mp ( this , & GameView : : _play_pressed ) ) ;
EditorRunBar : : get_singleton ( ) - > connect ( " stop_pressed " , callable_mp ( this , & GameView : : _stop_pressed ) ) ;
EditorRun : : instance_starting_callback = _instance_starting_static ;
2025-05-29 08:07:49 +03:00
EditorRun : : instance_rq_screenshot_callback = _instance_rq_screenshot_static ;
2024-09-25 16:57:23 -04:00
// Listen for project settings changes to update the window size and aspect ratio.
2025-01-20 18:33:12 -05:00
ProjectSettings : : get_singleton ( ) - > connect ( " settings_changed " , callable_mp ( this , & GameView : : _editor_or_project_settings_changed ) ) ;
EditorSettings : : get_singleton ( ) - > connect ( " settings_changed " , callable_mp ( this , & GameView : : _editor_or_project_settings_changed ) ) ;
2024-09-25 16:57:23 -04:00
} else {
// Embedding not available.
2025-03-21 07:00:49 +00:00
embedding_hb - > hide ( ) ;
2024-09-25 16:57:23 -04:00
}
_update_ui ( ) ;
} break ;
case NOTIFICATION_WM_POSITION_CHANGED : {
if ( window_wrapper - > get_window_enabled ( ) ) {
_update_floating_window_settings ( ) ;
}
} break ;
2024-09-18 18:07:18 -03:00
}
}
2024-09-25 16:57:23 -04:00
void GameView : : set_window_layout ( Ref < ConfigFile > p_layout ) {
floating_window_rect = p_layout - > get_value ( " GameView " , " floating_window_rect " , Rect2i ( ) ) ;
floating_window_screen = p_layout - > get_value ( " GameView " , " floating_window_screen " , - 1 ) ;
}
void GameView : : get_window_layout ( Ref < ConfigFile > p_layout ) {
if ( window_wrapper - > get_window_enabled ( ) ) {
_update_floating_window_settings ( ) ;
}
p_layout - > set_value ( " GameView " , " floating_window_rect " , floating_window_rect ) ;
p_layout - > set_value ( " GameView " , " floating_window_screen " , floating_window_screen ) ;
}
void GameView : : _update_floating_window_settings ( ) {
if ( window_wrapper - > get_window_enabled ( ) ) {
floating_window_rect = window_wrapper - > get_window_rect ( ) ;
floating_window_screen = window_wrapper - > get_window_screen ( ) ;
}
}
2025-01-19 15:45:59 -05:00
void GameView : : _attach_script_debugger ( ) {
2025-06-06 07:34:49 +10:00
_detach_script_debugger ( ) ;
2025-01-19 15:45:59 -05:00
2025-04-29 07:01:27 +10:00
int i = 0 ;
while ( ScriptEditorDebugger * script_debugger = EditorDebuggerNode : : get_singleton ( ) - > get_debugger ( i ) ) {
2025-01-19 15:45:59 -05:00
if ( script_debugger - > is_session_active ( ) & & script_debugger - > get_remote_pid ( ) = = embedded_process - > get_embedded_pid ( ) ) {
embedded_script_debugger = script_debugger ;
break ;
}
2025-04-29 07:01:27 +10:00
i + + ;
2025-01-19 15:45:59 -05:00
}
2025-04-29 07:01:27 +10:00
# ifdef MACOS_ENABLED
embedded_process - > set_script_debugger ( embedded_script_debugger ) ;
# endif
2025-01-19 15:45:59 -05:00
if ( embedded_script_debugger ) {
embedded_script_debugger - > connect ( " remote_window_title_changed " , callable_mp ( this , & GameView : : _remote_window_title_changed ) ) ;
2025-04-25 02:36:00 -03:00
embedded_script_debugger - > connect ( " embed_shortcut_requested " , callable_mp ( this , & GameView : : _handle_shortcut_requested ) ) ;
2025-01-19 15:45:59 -05:00
}
}
void GameView : : _detach_script_debugger ( ) {
if ( embedded_script_debugger ) {
embedded_script_debugger - > disconnect ( " remote_window_title_changed " , callable_mp ( this , & GameView : : _remote_window_title_changed ) ) ;
2025-04-25 02:36:00 -03:00
embedded_script_debugger - > disconnect ( " embed_shortcut_requested " , callable_mp ( this , & GameView : : _handle_shortcut_requested ) ) ;
2025-01-19 15:45:59 -05:00
embedded_script_debugger = nullptr ;
}
2025-06-06 07:34:49 +10:00
embedded_process - > set_script_debugger ( nullptr ) ;
2025-01-19 15:45:59 -05:00
}
void GameView : : _remote_window_title_changed ( String title ) {
window_wrapper - > set_window_title ( title ) ;
}
2024-09-25 16:57:23 -04:00
void GameView : : _update_arguments_for_instance ( int p_idx , List < String > & r_arguments ) {
2025-01-20 18:33:12 -05:00
if ( p_idx ! = 0 | | ! embed_on_play | | _get_embed_available ( ) ! = EMBED_AVAILABLE ) {
2024-09-25 16:57:23 -04:00
return ;
}
// Remove duplicates/unwanted parameters.
List < String > : : Element * E = r_arguments . front ( ) ;
2025-01-18 00:36:07 -05:00
List < String > : : Element * user_args_element = nullptr ;
2025-05-07 06:11:05 +10:00
HashSet < String > remove_args ( { " --position " , " --resolution " , " --screen " } ) ;
# ifdef MACOS_ENABLED
// macOS requires the embedded display driver.
remove_args . insert ( " --display-driver " ) ;
# endif
2025-11-15 23:38:41 +01:00
# ifdef WAYLAND_ENABLED
// Wayland requires its display driver.
if ( DisplayServer : : get_singleton ( ) - > get_name ( ) = = " Wayland " ) {
remove_args . insert ( " --display-driver " ) ;
}
# endif
# ifdef X11_ENABLED
// X11 requires its display driver.
if ( DisplayServer : : get_singleton ( ) - > get_name ( ) = = " X11 " ) {
remove_args . insert ( " --display-driver " ) ;
}
# endif
2024-09-25 16:57:23 -04:00
while ( E ) {
List < String > : : Element * N = E - > next ( ) ;
2025-05-07 06:11:05 +10:00
// For these parameters, we need to also remove the value.
if ( remove_args . has ( E - > get ( ) ) ) {
2024-09-25 16:57:23 -04:00
r_arguments . erase ( E ) ;
if ( N ) {
List < String > : : Element * V = N - > next ( ) ;
r_arguments . erase ( N ) ;
N = V ;
}
} else if ( E - > get ( ) = = " -f " | | E - > get ( ) = = " --fullscreen " | | E - > get ( ) = = " -m " | | E - > get ( ) = = " --maximized " | | E - > get ( ) = = " -t " | | E - > get ( ) = = " -always-on-top " ) {
r_arguments . erase ( E ) ;
2025-01-18 00:36:07 -05:00
} else if ( E - > get ( ) = = " -- " | | E - > get ( ) = = " ++ " ) {
user_args_element = E ;
break ;
2024-09-25 16:57:23 -04:00
}
E = N ;
}
// Add the editor window's native ID so the started game can directly set it as its parent.
2025-01-18 00:36:07 -05:00
List < String > : : Element * N = r_arguments . insert_before ( user_args_element , " --wid " ) ;
N = r_arguments . insert_after ( N , itos ( DisplayServer : : get_singleton ( ) - > window_get_native_handle ( DisplayServer : : WINDOW_HANDLE , get_window ( ) - > get_window_id ( ) ) ) ) ;
2024-09-25 16:57:23 -04:00
2025-04-29 07:01:27 +10:00
# if MACOS_ENABLED
2025-05-08 18:51:28 +10:00
N = r_arguments . insert_after ( N , " --embedded " ) ;
2025-04-29 07:01:27 +10:00
# endif
2025-11-15 23:38:41 +01:00
# ifdef WAYLAND_ENABLED
if ( DisplayServer : : get_singleton ( ) - > get_name ( ) = = " Wayland " ) {
N = r_arguments . insert_after ( N , " --display-driver " ) ;
N = r_arguments . insert_after ( N , " wayland " ) ;
}
# endif
# ifdef X11_ENABLED
if ( DisplayServer : : get_singleton ( ) - > get_name ( ) = = " X11 " ) {
N = r_arguments . insert_after ( N , " --display-driver " ) ;
N = r_arguments . insert_after ( N , " x11 " ) ;
}
# endif
2024-09-25 16:57:23 -04:00
// Be sure to have the correct window size in the embedded_process control.
_update_embed_window_size ( ) ;
Rect2i rect = embedded_process - > get_screen_embedded_window_rect ( ) ;
2025-02-11 21:41:03 -05:00
2025-03-01 10:31:31 -05:00
// Usually, the global rect of the embedded process control is invalid because it was hidden. We will calculate it manually.
if ( ! window_wrapper - > get_window_enabled ( ) ) {
2025-02-20 20:24:12 -05:00
Size2 old_min_size = embedded_process - > get_custom_minimum_size ( ) ;
embedded_process - > set_custom_minimum_size ( Size2i ( ) ) ;
Control * container = EditorNode : : get_singleton ( ) - > get_editor_main_screen ( ) - > get_control ( ) ;
rect = container - > get_global_rect ( ) ;
Size2 wrapped_min_size = window_wrapper - > get_minimum_size ( ) ;
rect . position . y + = wrapped_min_size . y ;
rect . size . y - = wrapped_min_size . y ;
rect = embedded_process - > get_adjusted_embedded_window_rect ( rect ) ;
embedded_process - > set_custom_minimum_size ( old_min_size ) ;
}
2025-02-11 21:41:03 -05:00
// When using the floating window, we need to force the position and size from the
// editor/project settings, because the get_screen_embedded_window_rect of the
// embedded_process will be updated only on the next frame.
2025-02-20 20:24:12 -05:00
if ( window_wrapper - > get_window_enabled ( ) ) {
2025-02-11 21:41:03 -05:00
EditorRun : : WindowPlacement placement = EditorRun : : get_window_placement ( ) ;
if ( placement . position ! = Point2i ( INT_MAX , INT_MAX ) ) {
rect . position = placement . position ;
}
if ( placement . size ! = Size2i ( ) ) {
rect . size = placement . size ;
}
}
2025-01-18 00:36:07 -05:00
N = r_arguments . insert_after ( N , " --position " ) ;
N = r_arguments . insert_after ( N , itos ( rect . position . x ) + " , " + itos ( rect . position . y ) ) ;
N = r_arguments . insert_after ( N , " --resolution " ) ;
r_arguments . insert_after ( N , itos ( rect . size . x ) + " x " + itos ( rect . size . y ) ) ;
2024-09-25 16:57:23 -04:00
}
2025-01-21 19:12:27 -05:00
void GameView : : _window_close_request ( ) {
2025-06-06 07:34:49 +10:00
if ( window_wrapper - > get_window_enabled ( ) ) {
window_wrapper - > set_window_enabled ( false ) ;
}
2024-09-25 16:57:23 -04:00
// Before the parent window closed, we close the embedded game. That prevents
// the embedded game to be seen without a parent window for a fraction of second.
if ( EditorRunBar : : get_singleton ( ) - > is_playing ( ) & & ( embedded_process - > is_embedding_completed ( ) | | embedded_process - > is_embedding_in_progress ( ) ) ) {
2025-01-21 19:12:27 -05:00
// When the embedding is not complete, we need to kill the process.
2025-01-24 20:44:47 -05:00
// If the game is paused, the close request will not be processed by the game, so it's better to kill the process.
if ( paused | | embedded_process - > is_embedding_in_progress ( ) ) {
2025-02-05 19:51:30 -05:00
embedded_process - > reset ( ) ;
2025-01-21 19:12:27 -05:00
// Call deferred to prevent the _stop_pressed callback to be executed before the wrapper window
// actually closes.
callable_mp ( EditorRunBar : : get_singleton ( ) , & EditorRunBar : : stop_playing ) . call_deferred ( ) ;
2025-02-05 19:51:30 -05:00
} else {
// Try to gracefully close the window. That way, the NOTIFICATION_WM_CLOSE_REQUEST
// notification should be propagated in the game process.
embedded_process - > request_close ( ) ;
2025-01-21 19:12:27 -05:00
}
2024-09-25 16:57:23 -04:00
}
}
2025-01-24 20:44:47 -05:00
void GameView : : _debugger_breaked ( bool p_breaked , bool p_can_debug ) {
if ( p_breaked = = paused ) {
return ;
}
paused = p_breaked ;
if ( paused ) {
size_paused = embedded_process - > get_screen_embedded_window_rect ( ) . size ;
}
_update_embed_window_size ( ) ;
}
2025-03-22 10:07:19 -03:00
void GameView : : _feature_profile_changed ( ) {
Ref < EditorFeatureProfile > profile = EditorFeatureProfileManager : : get_singleton ( ) - > get_current_profile ( ) ;
bool is_profile_null = profile . is_null ( ) ;
is_feature_enabled = is_profile_null | | ! profile - > is_feature_disabled ( EditorFeatureProfile : : FEATURE_GAME ) ;
bool is_3d_enabled = is_profile_null | | ! profile - > is_feature_disabled ( EditorFeatureProfile : : FEATURE_3D ) ;
if ( ! is_3d_enabled & & node_type_button [ RuntimeNodeSelect : : NODE_TYPE_3D ] - > is_pressed ( ) ) {
_node_type_pressed ( RuntimeNodeSelect : : NODE_TYPE_NONE ) ;
}
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_3D ] - > set_visible ( is_3d_enabled ) ;
}
2025-04-29 07:01:27 +10:00
GameView : : GameView ( Ref < GameViewDebugger > p_debugger , EmbeddedProcessBase * p_embedded_process , WindowWrapper * p_wrapper ) {
2024-09-25 16:57:23 -04:00
singleton = this ;
2024-09-18 18:07:18 -03:00
debugger = p_debugger ;
2024-09-25 16:57:23 -04:00
window_wrapper = p_wrapper ;
2025-04-29 07:01:27 +10:00
embedded_process = p_embedded_process ;
2024-09-18 18:07:18 -03:00
MarginContainer * toolbar_margin = memnew ( MarginContainer ) ;
2025-09-30 10:54:07 -03:00
toolbar_margin - > set_theme_type_variation ( " MainToolBarMargin " ) ;
2024-09-18 18:07:18 -03:00
add_child ( toolbar_margin ) ;
2025-03-21 07:00:49 +00:00
FlowContainer * main_menu_fc = memnew ( FlowContainer ) ;
toolbar_margin - > add_child ( main_menu_fc ) ;
2024-09-18 18:07:18 -03:00
2025-03-21 07:00:49 +00:00
HBoxContainer * process_hb = memnew ( HBoxContainer ) ;
main_menu_fc - > add_child ( process_hb ) ;
2024-09-18 18:07:18 -03:00
suspend_button = memnew ( Button ) ;
2025-03-21 07:00:49 +00:00
process_hb - > add_child ( suspend_button ) ;
2024-09-18 18:07:18 -03:00
suspend_button - > set_toggle_mode ( true ) ;
2024-12-05 17:13:20 +03:00
suspend_button - > set_theme_type_variation ( SceneStringName ( FlatButton ) ) ;
2024-09-18 18:07:18 -03:00
suspend_button - > connect ( SceneStringName ( toggled ) , callable_mp ( this , & GameView : : _suspend_button_toggled ) ) ;
2025-03-21 09:55:22 +02:00
suspend_button - > set_accessibility_name ( TTRC ( " Suspend " ) ) ;
2025-04-25 02:36:00 -03:00
suspend_button - > set_shortcut ( ED_GET_SHORTCUT ( " editor/suspend_resume_embedded_project " ) ) ;
2025-08-30 07:12:34 +03:00
suspend_button - > set_tooltip_text ( TTRC ( " Force pause at SceneTree level. Stops all processing, but you can still interact with the project. " ) ) ;
2024-09-18 18:07:18 -03:00
next_frame_button = memnew ( Button ) ;
2025-03-21 07:00:49 +00:00
process_hb - > add_child ( next_frame_button ) ;
2024-12-05 17:13:20 +03:00
next_frame_button - > set_theme_type_variation ( SceneStringName ( FlatButton ) ) ;
2024-09-18 18:07:18 -03:00
next_frame_button - > connect ( SceneStringName ( pressed ) , callable_mp ( * debugger , & GameViewDebugger : : next_frame ) ) ;
2025-03-21 09:55:22 +02:00
next_frame_button - > set_accessibility_name ( TTRC ( " Next Frame " ) ) ;
2025-06-10 22:15:02 -07:00
next_frame_button - > set_shortcut ( ED_GET_SHORTCUT ( " editor/next_frame_embedded_project " ) ) ;
2024-09-18 18:07:18 -03:00
2025-06-07 21:47:43 -07:00
speed_state_button = memnew ( MenuButton ) ;
2025-03-21 07:00:49 +00:00
process_hb - > add_child ( speed_state_button ) ;
2025-06-07 21:47:43 -07:00
speed_state_button - > set_text ( U " 1.0× " ) ;
speed_state_button - > set_theme_type_variation ( SceneStringName ( FlatButton ) ) ;
speed_state_button - > set_tooltip_text ( TTRC ( " Change the game speed. " ) ) ;
speed_state_button - > set_accessibility_name ( TTRC ( " Speed State " ) ) ;
PopupMenu * menu = speed_state_button - > get_popup ( ) ;
menu - > connect ( SceneStringName ( id_pressed ) , callable_mp ( this , & GameView : : _speed_state_menu_pressed ) ) ;
for ( String lbl : time_scale_label ) {
menu - > add_item ( vformat ( U " %s× " , lbl ) ) ;
}
reset_speed_button = memnew ( Button ) ;
2025-03-21 07:00:49 +00:00
process_hb - > add_child ( reset_speed_button ) ;
2025-06-07 21:47:43 -07:00
reset_speed_button - > set_theme_type_variation ( SceneStringName ( FlatButton ) ) ;
reset_speed_button - > set_tooltip_text ( TTRC ( " Reset the game speed. " ) ) ;
reset_speed_button - > set_accessibility_name ( TTRC ( " Reset Speed " ) ) ;
reset_speed_button - > connect ( SceneStringName ( pressed ) , callable_mp ( this , & GameView : : _reset_time_scales ) ) ;
2025-03-21 07:00:49 +00:00
process_hb - > add_child ( memnew ( VSeparator ) ) ;
HBoxContainer * input_hb = memnew ( HBoxContainer ) ;
main_menu_fc - > add_child ( input_hb ) ;
2024-09-18 18:07:18 -03:00
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_NONE ] = memnew ( Button ) ;
2025-03-21 07:00:49 +00:00
input_hb - > add_child ( node_type_button [ RuntimeNodeSelect : : NODE_TYPE_NONE ] ) ;
2025-05-30 00:10:53 +02:00
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_NONE ] - > set_text ( TTRC ( " Input " ) ) ;
2024-09-18 18:07:18 -03:00
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_NONE ] - > set_toggle_mode ( true ) ;
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_NONE ] - > set_pressed ( true ) ;
2024-12-05 17:13:20 +03:00
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_NONE ] - > set_theme_type_variation ( SceneStringName ( FlatButton ) ) ;
2024-09-18 18:07:18 -03:00
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_NONE ] - > connect ( SceneStringName ( pressed ) , callable_mp ( this , & GameView : : _node_type_pressed ) . bind ( RuntimeNodeSelect : : NODE_TYPE_NONE ) ) ;
2025-05-30 00:10:53 +02:00
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_NONE ] - > set_tooltip_text ( TTRC ( " Allow game input. " ) ) ;
2024-09-18 18:07:18 -03:00
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_2D ] = memnew ( Button ) ;
2025-03-21 07:00:49 +00:00
input_hb - > add_child ( node_type_button [ RuntimeNodeSelect : : NODE_TYPE_2D ] ) ;
2025-05-30 00:10:53 +02:00
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_2D ] - > set_text ( TTRC ( " 2D " ) ) ;
2024-09-18 18:07:18 -03:00
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_2D ] - > set_toggle_mode ( true ) ;
2024-12-05 17:13:20 +03:00
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_2D ] - > set_theme_type_variation ( SceneStringName ( FlatButton ) ) ;
2024-09-18 18:07:18 -03:00
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_2D ] - > connect ( SceneStringName ( pressed ) , callable_mp ( this , & GameView : : _node_type_pressed ) . bind ( RuntimeNodeSelect : : NODE_TYPE_2D ) ) ;
2025-05-30 00:10:53 +02:00
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_2D ] - > set_tooltip_text ( TTRC ( " Disable game input and allow to select Node2Ds, Controls, and manipulate the 2D camera. " ) ) ;
2024-09-18 18:07:18 -03:00
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_3D ] = memnew ( Button ) ;
2025-03-21 07:00:49 +00:00
input_hb - > add_child ( node_type_button [ RuntimeNodeSelect : : NODE_TYPE_3D ] ) ;
2025-05-30 00:10:53 +02:00
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_3D ] - > set_text ( TTRC ( " 3D " ) ) ;
2024-09-18 18:07:18 -03:00
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_3D ] - > set_toggle_mode ( true ) ;
2024-12-05 17:13:20 +03:00
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_3D ] - > set_theme_type_variation ( SceneStringName ( FlatButton ) ) ;
2024-09-18 18:07:18 -03:00
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_3D ] - > connect ( SceneStringName ( pressed ) , callable_mp ( this , & GameView : : _node_type_pressed ) . bind ( RuntimeNodeSelect : : NODE_TYPE_3D ) ) ;
2025-05-30 00:10:53 +02:00
node_type_button [ RuntimeNodeSelect : : NODE_TYPE_3D ] - > set_tooltip_text ( TTRC ( " Disable game input and allow to select Node3Ds and manipulate the 3D camera. " ) ) ;
2024-09-18 18:07:18 -03:00
2025-03-21 07:00:49 +00:00
input_hb - > add_child ( memnew ( VSeparator ) ) ;
2024-09-18 18:07:18 -03:00
2025-03-21 07:00:49 +00:00
HBoxContainer * selection_hb = memnew ( HBoxContainer ) ;
main_menu_fc - > add_child ( selection_hb ) ;
2024-09-18 18:07:18 -03:00
select_mode_button [ RuntimeNodeSelect : : SELECT_MODE_SINGLE ] = memnew ( Button ) ;
2025-03-21 07:00:49 +00:00
selection_hb - > add_child ( select_mode_button [ RuntimeNodeSelect : : SELECT_MODE_SINGLE ] ) ;
2024-09-18 18:07:18 -03:00
select_mode_button [ RuntimeNodeSelect : : SELECT_MODE_SINGLE ] - > set_toggle_mode ( true ) ;
select_mode_button [ RuntimeNodeSelect : : SELECT_MODE_SINGLE ] - > set_pressed ( true ) ;
2024-12-05 17:13:20 +03:00
select_mode_button [ RuntimeNodeSelect : : SELECT_MODE_SINGLE ] - > set_theme_type_variation ( SceneStringName ( FlatButton ) ) ;
2024-09-18 18:07:18 -03:00
select_mode_button [ RuntimeNodeSelect : : SELECT_MODE_SINGLE ] - > connect ( SceneStringName ( pressed ) , callable_mp ( this , & GameView : : _select_mode_pressed ) . bind ( RuntimeNodeSelect : : SELECT_MODE_SINGLE ) ) ;
2025-06-10 22:15:02 -07:00
select_mode_button [ RuntimeNodeSelect : : SELECT_MODE_SINGLE ] - > set_shortcut ( ED_GET_SHORTCUT ( " spatial_editor/tool_select " ) ) ;
2024-09-18 18:07:18 -03:00
select_mode_button [ RuntimeNodeSelect : : SELECT_MODE_SINGLE ] - > set_shortcut_context ( this ) ;
select_mode_button [ RuntimeNodeSelect : : SELECT_MODE_LIST ] = memnew ( Button ) ;
2025-03-21 07:00:49 +00:00
selection_hb - > add_child ( select_mode_button [ RuntimeNodeSelect : : SELECT_MODE_LIST ] ) ;
2024-09-18 18:07:18 -03:00
select_mode_button [ RuntimeNodeSelect : : SELECT_MODE_LIST ] - > set_toggle_mode ( true ) ;
2024-12-05 17:13:20 +03:00
select_mode_button [ RuntimeNodeSelect : : SELECT_MODE_LIST ] - > set_theme_type_variation ( SceneStringName ( FlatButton ) ) ;
2024-09-18 18:07:18 -03:00
select_mode_button [ RuntimeNodeSelect : : SELECT_MODE_LIST ] - > connect ( SceneStringName ( pressed ) , callable_mp ( this , & GameView : : _select_mode_pressed ) . bind ( RuntimeNodeSelect : : SELECT_MODE_LIST ) ) ;
2025-05-30 00:10:53 +02:00
select_mode_button [ RuntimeNodeSelect : : SELECT_MODE_LIST ] - > set_tooltip_text ( TTRC ( " Show list of selectable nodes at position clicked. " ) ) ;
2024-09-18 18:07:18 -03:00
2024-11-18 15:44:38 -03:00
_select_mode_pressed ( EditorSettings : : get_singleton ( ) - > get_project_metadata ( " game_view " , " select_mode " , 0 ) ) ;
2025-03-21 07:00:49 +00:00
hide_selection = memnew ( Button ) ;
selection_hb - > add_child ( hide_selection ) ;
hide_selection - > set_toggle_mode ( true ) ;
hide_selection - > set_theme_type_variation ( SceneStringName ( FlatButton ) ) ;
hide_selection - > connect ( SceneStringName ( toggled ) , callable_mp ( this , & GameView : : _hide_selection_toggled ) ) ;
hide_selection - > set_tooltip_text ( TTRC ( " Toggle Selection Visibility " ) ) ;
hide_selection - > set_pressed ( EditorSettings : : get_singleton ( ) - > get_project_metadata ( " game_view " , " hide_selection " , false ) ) ;
selection_hb - > add_child ( memnew ( VSeparator ) ) ;
HBoxContainer * audio_hb = memnew ( HBoxContainer ) ;
main_menu_fc - > add_child ( audio_hb ) ;
2024-11-21 21:31:00 -08:00
debug_mute_audio_button = memnew ( Button ) ;
2025-03-21 07:00:49 +00:00
audio_hb - > add_child ( debug_mute_audio_button ) ;
2024-11-21 21:31:00 -08:00
debug_mute_audio_button - > set_theme_type_variation ( " FlatButton " ) ;
debug_mute_audio_button - > connect ( SceneStringName ( pressed ) , callable_mp ( this , & GameView : : _debug_mute_audio_button_pressed ) ) ;
debug_mute_audio_button - > set_tooltip_text ( debug_mute_audio ? TTRC ( " Unmute game audio. " ) : TTRC ( " Mute game audio. " ) ) ;
2025-03-21 07:00:49 +00:00
audio_hb - > add_child ( memnew ( VSeparator ) ) ;
HBoxContainer * camera_hb = memnew ( HBoxContainer ) ;
main_menu_fc - > add_child ( camera_hb ) ;
2024-09-18 18:07:18 -03:00
camera_override_button = memnew ( Button ) ;
2025-03-21 07:00:49 +00:00
camera_hb - > add_child ( camera_override_button ) ;
2024-09-18 18:07:18 -03:00
camera_override_button - > set_toggle_mode ( true ) ;
2024-12-05 17:13:20 +03:00
camera_override_button - > set_theme_type_variation ( SceneStringName ( FlatButton ) ) ;
2025-05-30 00:10:53 +02:00
camera_override_button - > set_tooltip_text ( TTRC ( " Override the in-game camera. " ) ) ;
2024-11-18 15:44:38 -03:00
camera_override_button - > connect ( SceneStringName ( toggled ) , callable_mp ( this , & GameView : : _camera_override_button_toggled ) ) ;
2024-09-18 18:07:18 -03:00
camera_override_menu = memnew ( MenuButton ) ;
2025-03-21 07:00:49 +00:00
camera_hb - > add_child ( camera_override_menu ) ;
2024-09-18 18:07:18 -03:00
camera_override_menu - > set_flat ( false ) ;
camera_override_menu - > set_theme_type_variation ( " FlatMenuButton " ) ;
camera_override_menu - > set_h_size_flags ( SIZE_SHRINK_END ) ;
2025-05-30 00:10:53 +02:00
camera_override_menu - > set_tooltip_text ( TTRC ( " Camera Override Options " ) ) ;
2024-09-18 18:07:18 -03:00
2025-06-07 21:47:43 -07:00
menu = camera_override_menu - > get_popup ( ) ;
2024-09-18 18:07:18 -03:00
menu - > connect ( SceneStringName ( id_pressed ) , callable_mp ( this , & GameView : : _camera_override_menu_id_pressed ) ) ;
2025-05-30 00:10:53 +02:00
menu - > add_item ( TTRC ( " Reset 2D Camera " ) , CAMERA_RESET_2D ) ;
menu - > add_item ( TTRC ( " Reset 3D Camera " ) , CAMERA_RESET_3D ) ;
2024-09-18 18:07:18 -03:00
menu - > add_separator ( ) ;
2025-05-30 00:10:53 +02:00
menu - > add_radio_check_item ( TTRC ( " Manipulate In-Game " ) , CAMERA_MODE_INGAME ) ;
2024-09-18 18:07:18 -03:00
menu - > set_item_checked ( menu - > get_item_index ( CAMERA_MODE_INGAME ) , true ) ;
2025-05-30 00:10:53 +02:00
menu - > add_radio_check_item ( TTRC ( " Manipulate From Editors " ) , CAMERA_MODE_EDITORS ) ;
2025-04-29 07:01:27 +10:00
_camera_override_menu_id_pressed ( EditorSettings : : get_singleton ( ) - > get_project_metadata ( " game_view " , " camera_override_mode " , 0 ) ) ;
2024-09-18 18:07:18 -03:00
2025-03-21 07:00:49 +00:00
camera_hb - > add_child ( memnew ( VSeparator ) ) ;
embedding_hb = memnew ( HBoxContainer ) ;
embedding_hb - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
main_menu_fc - > add_child ( embedding_hb ) ;
2024-09-25 16:57:23 -04:00
embed_options_menu = memnew ( MenuButton ) ;
2025-03-21 07:00:49 +00:00
embedding_hb - > add_child ( embed_options_menu ) ;
2024-09-25 16:57:23 -04:00
embed_options_menu - > set_flat ( false ) ;
embed_options_menu - > set_theme_type_variation ( " FlatMenuButton " ) ;
embed_options_menu - > set_h_size_flags ( SIZE_SHRINK_END ) ;
2025-05-30 00:10:53 +02:00
embed_options_menu - > set_tooltip_text ( TTRC ( " Embedding Options " ) ) ;
2024-09-25 16:57:23 -04:00
menu = embed_options_menu - > get_popup ( ) ;
menu - > connect ( SceneStringName ( id_pressed ) , callable_mp ( this , & GameView : : _embed_options_menu_menu_id_pressed ) ) ;
2025-05-30 00:10:53 +02:00
menu - > add_check_item ( TTRC ( " Embed Game on Next Play " ) , EMBED_RUN_GAME_EMBEDDED ) ;
menu - > add_check_item ( TTRC ( " Make Game Workspace Floating on Next Play " ) , EMBED_MAKE_FLOATING_ON_PLAY ) ;
2025-03-21 07:00:49 +00:00
menu - > add_separator ( TTRC ( " Embedded Window Sizing " ) ) ;
2024-09-25 16:57:23 -04:00
2025-03-21 07:00:49 +00:00
menu - > add_radio_check_item ( TTRC ( " Fixed Size " ) , SIZE_MODE_FIXED ) ;
menu - > set_item_tooltip ( menu - > get_item_index ( SIZE_MODE_FIXED ) , TTRC ( " Embedded game size is based on project settings. \n The 'Keep Aspect' mode is used when the Game Workspace is smaller than the desired size. " ) ) ;
menu - > add_radio_check_item ( TTRC ( " Keep Aspect Ratio " ) , SIZE_MODE_KEEP_ASPECT ) ;
menu - > set_item_tooltip ( menu - > get_item_index ( SIZE_MODE_KEEP_ASPECT ) , TTRC ( " Keep the aspect ratio of the embedded game. " ) ) ;
menu - > add_radio_check_item ( TTRC ( " Stretch to Fit " ) , SIZE_MODE_STRETCH ) ;
menu - > set_item_tooltip ( menu - > get_item_index ( SIZE_MODE_STRETCH ) , TTRC ( " Embedded game size stretches to fit the Game Workspace. " ) ) ;
2024-09-25 16:57:23 -04:00
game_size_label = memnew ( Label ( ) ) ;
2025-03-21 07:00:49 +00:00
embedding_hb - > add_child ( game_size_label ) ;
2024-09-25 16:57:23 -04:00
game_size_label - > hide ( ) ;
2025-01-31 12:02:06 -05:00
// Setting the minimum size prevents the game workspace from resizing indefinitely
// due to the label size oscillating by a few pixels when the game is in stretch mode
// and the game workspace is at its minimum size.
game_size_label - > set_custom_minimum_size ( Size2 ( 80 * EDSCALE , 0 ) ) ;
2025-03-21 07:00:49 +00:00
game_size_label - > set_h_size_flags ( SIZE_EXPAND_FILL ) ;
2025-01-31 12:02:06 -05:00
game_size_label - > set_horizontal_alignment ( HorizontalAlignment : : HORIZONTAL_ALIGNMENT_RIGHT ) ;
2024-09-18 18:07:18 -03:00
2025-03-21 07:00:49 +00:00
panel = memnew ( PanelContainer ) ;
2024-09-18 18:07:18 -03:00
add_child ( panel ) ;
panel - > set_theme_type_variation ( " GamePanel " ) ;
panel - > set_v_size_flags ( SIZE_EXPAND_FILL ) ;
2025-03-21 07:00:49 +00:00
# ifdef MACOS_ENABLED
panel - > set_mouse_filter ( Control : : MOUSE_FILTER_IGNORE ) ;
# endif
2024-09-18 18:07:18 -03:00
2024-09-25 16:57:23 -04:00
panel - > add_child ( embedded_process ) ;
embedded_process - > set_anchors_and_offsets_preset ( PRESET_FULL_RECT ) ;
embedded_process - > connect ( " embedding_failed " , callable_mp ( this , & GameView : : _embedding_failed ) ) ;
embedded_process - > connect ( " embedding_completed " , callable_mp ( this , & GameView : : _embedding_completed ) ) ;
embedded_process - > connect ( " embedded_process_updated " , callable_mp ( this , & GameView : : _embedded_process_updated ) ) ;
2024-12-29 18:16:19 -05:00
embedded_process - > connect ( " embedded_process_focused " , callable_mp ( this , & GameView : : _embedded_process_focused ) ) ;
2024-09-25 16:57:23 -04:00
embedded_process - > set_custom_minimum_size ( Size2i ( 100 , 100 ) ) ;
state_label = memnew ( Label ( ) ) ;
2025-03-21 07:00:49 +00:00
panel - > add_child ( state_label ) ;
2024-09-25 16:57:23 -04:00
state_label - > set_horizontal_alignment ( HORIZONTAL_ALIGNMENT_CENTER ) ;
state_label - > set_vertical_alignment ( VERTICAL_ALIGNMENT_CENTER ) ;
state_label - > set_autowrap_mode ( TextServer : : AUTOWRAP_WORD ) ;
state_label - > set_anchors_and_offsets_preset ( PRESET_FULL_RECT ) ;
_update_debugger_buttons ( ) ;
2024-09-18 18:07:18 -03:00
p_debugger - > connect ( " session_started " , callable_mp ( this , & GameView : : _sessions_changed ) ) ;
p_debugger - > connect ( " session_stopped " , callable_mp ( this , & GameView : : _sessions_changed ) ) ;
2024-09-25 16:57:23 -04:00
2025-01-21 19:12:27 -05:00
p_wrapper - > set_override_close_request ( true ) ;
p_wrapper - > connect ( " window_close_requested " , callable_mp ( this , & GameView : : _window_close_request ) ) ;
2024-09-25 16:57:23 -04:00
p_wrapper - > connect ( " window_size_changed " , callable_mp ( this , & GameView : : _update_floating_window_settings ) ) ;
2025-01-24 20:44:47 -05:00
EditorDebuggerNode : : get_singleton ( ) - > connect ( " breaked " , callable_mp ( this , & GameView : : _debugger_breaked ) ) ;
2025-03-22 10:07:19 -03:00
EditorFeatureProfileManager : : get_singleton ( ) - > connect ( " current_feature_profile_changed " , callable_mp ( this , & GameView : : _feature_profile_changed ) ) ;
2024-09-18 18:07:18 -03:00
}
///////
2025-04-29 07:01:27 +10:00
void GameViewPluginBase : : selected_notify ( ) {
2025-01-07 21:31:53 -08:00
if ( _is_window_wrapper_enabled ( ) ) {
# ifdef ANDROID_ENABLED
notify_main_screen_changed ( get_plugin_name ( ) ) ;
# else
window_wrapper - > grab_window_focus ( ) ;
2024-11-18 15:44:38 -03:00
# endif // ANDROID_ENABLED
2025-01-07 21:31:53 -08:00
_focus_another_editor ( ) ;
}
}
# ifndef ANDROID_ENABLED
2025-04-29 07:01:27 +10:00
void GameViewPluginBase : : make_visible ( bool p_visible ) {
2024-09-25 16:57:23 -04:00
if ( p_visible ) {
window_wrapper - > show ( ) ;
} else {
window_wrapper - > hide ( ) ;
}
}
2025-04-29 07:01:27 +10:00
void GameViewPluginBase : : set_window_layout ( Ref < ConfigFile > p_layout ) {
2024-09-25 16:57:23 -04:00
game_view - > set_window_layout ( p_layout ) ;
}
2025-04-29 07:01:27 +10:00
void GameViewPluginBase : : get_window_layout ( Ref < ConfigFile > p_layout ) {
2024-09-25 16:57:23 -04:00
game_view - > get_window_layout ( p_layout ) ;
2024-09-18 18:07:18 -03:00
}
2025-04-29 07:01:27 +10:00
void GameViewPluginBase : : setup ( Ref < GameViewDebugger > p_debugger , EmbeddedProcessBase * p_embedded_process ) {
debugger = p_debugger ;
window_wrapper = memnew ( WindowWrapper ) ;
window_wrapper - > set_margins_enabled ( true ) ;
game_view = memnew ( GameView ( debugger , p_embedded_process , window_wrapper ) ) ;
game_view - > set_v_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
window_wrapper - > set_wrapped_control ( game_view , nullptr ) ;
EditorNode : : get_singleton ( ) - > get_editor_main_screen ( ) - > get_control ( ) - > add_child ( window_wrapper ) ;
window_wrapper - > set_v_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
window_wrapper - > hide ( ) ;
window_wrapper - > connect ( " window_visibility_changed " , callable_mp ( this , & GameViewPlugin : : _focus_another_editor ) . unbind ( 1 ) ) ;
}
2024-11-18 15:44:38 -03:00
# endif // ANDROID_ENABLED
2025-01-07 21:31:53 -08:00
2025-04-29 07:01:27 +10:00
void GameViewPluginBase : : _notification ( int p_what ) {
2024-09-18 18:07:18 -03:00
switch ( p_what ) {
2025-05-30 00:10:53 +02:00
case NOTIFICATION_TRANSLATION_CHANGED : {
# ifndef ANDROID_ENABLED
window_wrapper - > set_window_title ( vformat ( TTR ( " %s - Godot Engine " ) , TTR ( " Game Workspace " ) ) ) ;
# endif
} break ;
2024-09-18 18:07:18 -03:00
case NOTIFICATION_ENTER_TREE : {
add_debugger_plugin ( debugger ) ;
2025-04-29 07:01:27 +10:00
connect ( " main_screen_changed " , callable_mp ( this , & GameViewPluginBase : : _save_last_editor ) ) ;
2024-09-18 18:07:18 -03:00
} break ;
case NOTIFICATION_EXIT_TREE : {
remove_debugger_plugin ( debugger ) ;
2025-04-29 07:01:27 +10:00
disconnect ( " main_screen_changed " , callable_mp ( this , & GameViewPluginBase : : _save_last_editor ) ) ;
2024-09-18 18:07:18 -03:00
} break ;
}
}
2025-04-29 07:01:27 +10:00
void GameViewPluginBase : : _save_last_editor ( const String & p_editor ) {
2025-01-24 20:44:47 -05:00
if ( p_editor ! = get_plugin_name ( ) ) {
2024-09-25 16:57:23 -04:00
last_editor = p_editor ;
}
}
2025-04-29 07:01:27 +10:00
void GameViewPluginBase : : _focus_another_editor ( ) {
2025-01-07 21:31:53 -08:00
if ( _is_window_wrapper_enabled ( ) ) {
2025-01-24 20:44:47 -05:00
if ( last_editor . is_empty ( ) ) {
EditorNode : : get_singleton ( ) - > get_editor_main_screen ( ) - > select ( EditorMainScreen : : EDITOR_2D ) ;
} else {
EditorInterface : : get_singleton ( ) - > set_main_screen_editor ( last_editor ) ;
}
2024-09-25 16:57:23 -04:00
}
}
2025-04-29 07:01:27 +10:00
bool GameViewPluginBase : : _is_window_wrapper_enabled ( ) const {
2025-01-07 21:31:53 -08:00
# ifdef ANDROID_ENABLED
return true ;
# else
return window_wrapper - > get_window_enabled ( ) ;
2024-11-18 15:44:38 -03:00
# endif // ANDROID_ENABLED
2025-01-07 21:31:53 -08:00
}
2025-04-29 07:01:27 +10:00
GameViewPluginBase : : GameViewPluginBase ( ) {
2025-06-07 12:33:57 +05:30
# ifdef ANDROID_ENABLED
debugger . instantiate ( ) ;
# endif
2025-04-29 07:01:27 +10:00
}
2025-01-07 21:31:53 -08:00
2025-04-29 07:01:27 +10:00
GameViewPlugin : : GameViewPlugin ( ) :
GameViewPluginBase ( ) {
2025-01-07 21:31:53 -08:00
# ifndef ANDROID_ENABLED
2025-04-29 07:01:27 +10:00
Ref < GameViewDebugger > game_view_debugger ;
game_view_debugger . instantiate ( ) ;
EmbeddedProcess * embedded_process = memnew ( EmbeddedProcess ) ;
setup ( game_view_debugger , embedded_process ) ;
# endif
2024-09-18 18:07:18 -03:00
}