game_view_plugin.cpp 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365
  1. /**************************************************************************/
  2. /* game_view_plugin.cpp */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #include "game_view_plugin.h"
  31. #include "core/config/project_settings.h"
  32. #include "core/debugger/debugger_marshalls.h"
  33. #include "core/debugger/engine_debugger.h"
  34. #include "core/string/translation_server.h"
  35. #include "editor/debugger/editor_debugger_node.h"
  36. #include "editor/debugger/script_editor_debugger.h"
  37. #include "editor/editor_feature_profile.h"
  38. #include "editor/editor_interface.h"
  39. #include "editor/editor_main_screen.h"
  40. #include "editor/editor_node.h"
  41. #include "editor/editor_settings.h"
  42. #include "editor/editor_string_names.h"
  43. #include "editor/gui/editor_bottom_panel.h"
  44. #include "editor/gui/editor_run_bar.h"
  45. #include "editor/plugins/embedded_process.h"
  46. #include "editor/themes/editor_scale.h"
  47. #include "editor/window_wrapper.h"
  48. #include "scene/gui/button.h"
  49. #include "scene/gui/label.h"
  50. #include "scene/gui/menu_button.h"
  51. #include "scene/gui/panel.h"
  52. #include "scene/gui/separator.h"
  53. void GameViewDebugger::_session_started(Ref<EditorDebuggerSession> p_session) {
  54. if (!is_feature_enabled) {
  55. return;
  56. }
  57. Dictionary settings;
  58. settings["debugger/max_node_selection"] = EDITOR_GET("debugger/max_node_selection");
  59. settings["editors/panning/2d_editor_panning_scheme"] = EDITOR_GET("editors/panning/2d_editor_panning_scheme");
  60. settings["editors/panning/simple_panning"] = EDITOR_GET("editors/panning/simple_panning");
  61. settings["editors/panning/warped_mouse_panning"] = EDITOR_GET("editors/panning/warped_mouse_panning");
  62. settings["editors/panning/2d_editor_pan_speed"] = EDITOR_GET("editors/panning/2d_editor_pan_speed");
  63. settings["editors/polygon_editor/point_grab_radius"] = EDITOR_GET("editors/polygon_editor/point_grab_radius");
  64. settings["canvas_item_editor/pan_view"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("canvas_item_editor/pan_view"));
  65. settings["box_selection_fill_color"] = EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("box_selection_fill_color"), EditorStringName(Editor));
  66. settings["box_selection_stroke_color"] = EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("box_selection_stroke_color"), EditorStringName(Editor));
  67. settings["editors/3d/default_fov"] = EDITOR_GET("editors/3d/default_fov");
  68. settings["editors/3d/default_z_near"] = EDITOR_GET("editors/3d/default_z_near");
  69. settings["editors/3d/default_z_far"] = EDITOR_GET("editors/3d/default_z_far");
  70. settings["editors/3d/navigation/invert_x_axis"] = EDITOR_GET("editors/3d/navigation/invert_x_axis");
  71. settings["editors/3d/navigation/invert_y_axis"] = EDITOR_GET("editors/3d/navigation/invert_y_axis");
  72. settings["editors/3d/navigation/warped_mouse_panning"] = EDITOR_GET("editors/3d/navigation/warped_mouse_panning");
  73. settings["editors/3d/freelook/freelook_base_speed"] = EDITOR_GET("editors/3d/freelook/freelook_base_speed");
  74. settings["editors/3d/freelook/freelook_sensitivity"] = EDITOR_GET("editors/3d/freelook/freelook_sensitivity");
  75. settings["editors/3d/navigation_feel/orbit_sensitivity"] = EDITOR_GET("editors/3d/navigation_feel/orbit_sensitivity");
  76. settings["editors/3d/navigation_feel/translation_sensitivity"] = EDITOR_GET("editors/3d/navigation_feel/translation_sensitivity");
  77. settings["editors/3d/selection_box_color"] = EDITOR_GET("editors/3d/selection_box_color");
  78. settings["editors/3d/freelook/freelook_base_speed"] = EDITOR_GET("editors/3d/freelook/freelook_base_speed");
  79. Array setup_data;
  80. setup_data.append(settings);
  81. p_session->send_message("scene:runtime_node_select_setup", setup_data);
  82. Array type;
  83. type.append(node_type);
  84. p_session->send_message("scene:runtime_node_select_set_type", type);
  85. Array visible;
  86. visible.append(selection_visible);
  87. p_session->send_message("scene:runtime_node_select_set_visible", visible);
  88. Array mode;
  89. mode.append(select_mode);
  90. p_session->send_message("scene:runtime_node_select_set_mode", mode);
  91. Array mute_audio_data;
  92. mute_audio_data.append(mute_audio);
  93. p_session->send_message("scene:debug_mute_audio", mute_audio_data);
  94. Dictionary shortcut_settings;
  95. shortcut_settings["editor/suspend_resume_embedded_project"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("editor/suspend_resume_embedded_project"));
  96. shortcut_settings["editor/next_frame_embedded_project"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("editor/next_frame_embedded_project"));
  97. p_session->send_message("scene:setup_embedded_shortcuts", { shortcut_settings });
  98. emit_signal(SNAME("session_started"));
  99. }
  100. void GameViewDebugger::_session_stopped() {
  101. if (!is_feature_enabled) {
  102. return;
  103. }
  104. emit_signal(SNAME("session_stopped"));
  105. }
  106. void GameViewDebugger::set_suspend(bool p_enabled) {
  107. Array message;
  108. message.append(p_enabled);
  109. for (Ref<EditorDebuggerSession> &I : sessions) {
  110. if (I->is_active()) {
  111. I->send_message("scene:suspend_changed", message);
  112. }
  113. }
  114. }
  115. void GameViewDebugger::next_frame() {
  116. for (Ref<EditorDebuggerSession> &I : sessions) {
  117. if (I->is_active()) {
  118. I->send_message("scene:next_frame", Array());
  119. }
  120. }
  121. }
  122. void GameViewDebugger::set_node_type(int p_type) {
  123. node_type = p_type;
  124. Array message;
  125. message.append(p_type);
  126. for (Ref<EditorDebuggerSession> &I : sessions) {
  127. if (I->is_active()) {
  128. I->send_message("scene:runtime_node_select_set_type", message);
  129. }
  130. }
  131. }
  132. void GameViewDebugger::set_selection_visible(bool p_visible) {
  133. selection_visible = p_visible;
  134. Array message;
  135. message.append(p_visible);
  136. for (Ref<EditorDebuggerSession> &I : sessions) {
  137. if (I->is_active()) {
  138. I->send_message("scene:runtime_node_select_set_visible", message);
  139. }
  140. }
  141. }
  142. void GameViewDebugger::set_select_mode(int p_mode) {
  143. select_mode = p_mode;
  144. Array message;
  145. message.append(p_mode);
  146. for (Ref<EditorDebuggerSession> &I : sessions) {
  147. if (I->is_active()) {
  148. I->send_message("scene:runtime_node_select_set_mode", message);
  149. }
  150. }
  151. }
  152. void GameViewDebugger::set_debug_mute_audio(bool p_enabled) {
  153. mute_audio = p_enabled;
  154. EditorDebuggerNode::get_singleton()->set_debug_mute_audio(p_enabled);
  155. }
  156. void GameViewDebugger::set_camera_override(bool p_enabled) {
  157. EditorDebuggerNode::get_singleton()->set_camera_override(p_enabled ? camera_override_mode : EditorDebuggerNode::OVERRIDE_NONE);
  158. }
  159. void GameViewDebugger::set_camera_manipulate_mode(EditorDebuggerNode::CameraOverride p_mode) {
  160. camera_override_mode = p_mode;
  161. if (EditorDebuggerNode::get_singleton()->get_camera_override() != EditorDebuggerNode::OVERRIDE_NONE) {
  162. set_camera_override(true);
  163. }
  164. }
  165. void GameViewDebugger::reset_camera_2d_position() {
  166. for (Ref<EditorDebuggerSession> &I : sessions) {
  167. if (I->is_active()) {
  168. I->send_message("scene:runtime_node_select_reset_camera_2d", Array());
  169. }
  170. }
  171. }
  172. void GameViewDebugger::reset_camera_3d_position() {
  173. for (Ref<EditorDebuggerSession> &I : sessions) {
  174. if (I->is_active()) {
  175. I->send_message("scene:runtime_node_select_reset_camera_3d", Array());
  176. }
  177. }
  178. }
  179. void GameViewDebugger::setup_session(int p_session_id) {
  180. Ref<EditorDebuggerSession> session = get_session(p_session_id);
  181. ERR_FAIL_COND(session.is_null());
  182. sessions.append(session);
  183. session->connect("started", callable_mp(this, &GameViewDebugger::_session_started).bind(session));
  184. session->connect("stopped", callable_mp(this, &GameViewDebugger::_session_stopped));
  185. }
  186. void GameViewDebugger::_feature_profile_changed() {
  187. Ref<EditorFeatureProfile> profile = EditorFeatureProfileManager::get_singleton()->get_current_profile();
  188. is_feature_enabled = profile.is_null() || !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_GAME);
  189. }
  190. void GameViewDebugger::_bind_methods() {
  191. ADD_SIGNAL(MethodInfo("session_started"));
  192. ADD_SIGNAL(MethodInfo("session_stopped"));
  193. }
  194. bool GameViewDebugger::add_screenshot_callback(const Callable &p_callaback, const Rect2i &p_rect) {
  195. bool found = false;
  196. for (Ref<EditorDebuggerSession> &I : sessions) {
  197. if (I->is_active()) {
  198. ScreenshotCB sd;
  199. sd.cb = p_callaback;
  200. sd.rect = p_rect;
  201. screenshot_callbacks[scr_rq_id] = sd;
  202. Array arr;
  203. arr.append(scr_rq_id);
  204. I->send_message("scene:rq_screenshot", arr);
  205. scr_rq_id++;
  206. found = true;
  207. }
  208. }
  209. return found;
  210. }
  211. bool GameViewDebugger::_msg_get_screenshot(const Array &p_args) {
  212. ERR_FAIL_COND_V_MSG(p_args.size() != 4, false, "get_screenshot: invalid number of arguments");
  213. int64_t id = p_args[0];
  214. int64_t w = p_args[1];
  215. int64_t h = p_args[2];
  216. const String &path = p_args[3];
  217. if (screenshot_callbacks.has(id)) {
  218. if (screenshot_callbacks[id].cb.is_valid()) {
  219. screenshot_callbacks[id].cb.call(w, h, path, screenshot_callbacks[id].rect);
  220. }
  221. screenshot_callbacks.erase(id);
  222. }
  223. return true;
  224. }
  225. bool GameViewDebugger::capture(const String &p_message, const Array &p_data, int p_session) {
  226. Ref<EditorDebuggerSession> session = get_session(p_session);
  227. ERR_FAIL_COND_V(session.is_null(), true);
  228. if (p_message == "game_view:get_screenshot") {
  229. return _msg_get_screenshot(p_data);
  230. } else {
  231. // Any other messages with this prefix should be ignored.
  232. WARN_PRINT("GameViewDebugger unknown message: " + p_message);
  233. return false;
  234. }
  235. return true;
  236. }
  237. bool GameViewDebugger::has_capture(const String &p_capture) const {
  238. return p_capture == "game_view";
  239. }
  240. GameViewDebugger::GameViewDebugger() {
  241. EditorFeatureProfileManager::get_singleton()->connect("current_feature_profile_changed", callable_mp(this, &GameViewDebugger::_feature_profile_changed));
  242. ED_SHORTCUT("editor/suspend_resume_embedded_project", TTRC("Suspend/Resume Embedded Project"), Key::F9);
  243. ED_SHORTCUT_OVERRIDE("editor/suspend_resume_embedded_project", "macos", KeyModifierMask::META | KeyModifierMask::SHIFT | Key::B);
  244. ED_SHORTCUT("editor/next_frame_embedded_project", TTRC("Next Frame"), Key::F10);
  245. ED_SHORTCUT("spatial_editor/tool_select", TTRC("Select Mode"), Key::Q);
  246. }
  247. ///////
  248. void GameView::_sessions_changed() {
  249. // The debugger session's `session_started/stopped` signal can be unreliable, so count it manually.
  250. active_sessions = 0;
  251. Array sessions = debugger->get_sessions();
  252. for (int i = 0; i < sessions.size(); i++) {
  253. if (Object::cast_to<EditorDebuggerSession>(sessions[i])->is_active()) {
  254. active_sessions++;
  255. }
  256. }
  257. _update_debugger_buttons();
  258. #ifdef MACOS_ENABLED
  259. if (!embedded_script_debugger || !embedded_script_debugger->is_session_active() || embedded_script_debugger->get_remote_pid() != embedded_process->get_embedded_pid()) {
  260. _attach_script_debugger();
  261. }
  262. #else
  263. if (embedded_process->is_embedding_completed()) {
  264. if (!embedded_script_debugger || !embedded_script_debugger->is_session_active() || embedded_script_debugger->get_remote_pid() != embedded_process->get_embedded_pid()) {
  265. _attach_script_debugger();
  266. }
  267. }
  268. #endif
  269. }
  270. void GameView::_instance_starting_static(int p_idx, List<String> &r_arguments) {
  271. ERR_FAIL_NULL(singleton);
  272. singleton->_instance_starting(p_idx, r_arguments);
  273. }
  274. void GameView::_instance_starting(int p_idx, List<String> &r_arguments) {
  275. if (!is_feature_enabled) {
  276. return;
  277. }
  278. 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) {
  279. // Set the Floating Window default title. Always considered in DEBUG mode, same as in Window::set_title.
  280. String appname = GLOBAL_GET("application/config/name");
  281. appname = vformat("%s (DEBUG)", TranslationServer::get_singleton()->translate(appname));
  282. window_wrapper->set_window_title(appname);
  283. _show_update_window_wrapper();
  284. embedded_process->grab_focus();
  285. }
  286. _update_arguments_for_instance(p_idx, r_arguments);
  287. }
  288. bool GameView::_instance_rq_screenshot_static(const Callable &p_callback) {
  289. ERR_FAIL_NULL_V(singleton, false);
  290. return singleton->_instance_rq_screenshot(p_callback);
  291. }
  292. bool GameView::_instance_rq_screenshot(const Callable &p_callback) {
  293. if (debugger.is_null() || window_wrapper->get_window_enabled() || !embedded_process || !embedded_process->is_embedding_completed()) {
  294. return false;
  295. }
  296. Rect2 r = embedded_process->get_adjusted_embedded_window_rect(embedded_process->get_rect());
  297. r.position += embedded_process->get_global_position();
  298. #ifndef MACOS_ENABLED
  299. r.position -= embedded_process->get_window()->get_position();
  300. #endif
  301. return debugger->add_screenshot_callback(p_callback, r);
  302. }
  303. void GameView::_show_update_window_wrapper() {
  304. EditorRun::WindowPlacement placement = EditorRun::get_window_placement();
  305. Point2 position = floating_window_rect.position;
  306. Size2i size = floating_window_rect.size;
  307. int screen = floating_window_screen;
  308. // Obtain the size around the embedded process control. Usually, the difference between the game view's get_size
  309. // and the embedded control should work. However, when the control is hidden and has never been displayed,
  310. // the size of the embedded control is not calculated.
  311. Size2 old_min_size = embedded_process->get_custom_minimum_size();
  312. embedded_process->set_custom_minimum_size(Size2i());
  313. Size2 embedded_process_min_size = get_minimum_size();
  314. Size2 wrapped_margins_size = window_wrapper->get_margins_size();
  315. Size2 wrapped_min_size = window_wrapper->get_minimum_size();
  316. Point2 offset_embedded_process = embedded_process->get_global_position() - get_global_position();
  317. // On the first startup, the global position of the embedded process control is invalid because it was
  318. // never displayed. We will calculate it manually using the minimum size of the window.
  319. if (offset_embedded_process == Point2()) {
  320. offset_embedded_process.y = wrapped_min_size.y;
  321. }
  322. offset_embedded_process.x += embedded_process->get_margin_size(SIDE_LEFT);
  323. offset_embedded_process.y += embedded_process->get_margin_size(SIDE_TOP);
  324. offset_embedded_process += window_wrapper->get_margins_top_left();
  325. embedded_process->set_custom_minimum_size(old_min_size);
  326. Point2 size_diff_embedded_process = Point2(0, embedded_process_min_size.y) + embedded_process->get_margins_size();
  327. if (placement.position != Point2i(INT_MAX, INT_MAX)) {
  328. position = placement.position - offset_embedded_process;
  329. screen = placement.screen;
  330. }
  331. if (placement.size != Size2i()) {
  332. size = placement.size + size_diff_embedded_process + wrapped_margins_size;
  333. }
  334. window_wrapper->restore_window_from_saved_position(Rect2(position, size), screen, Rect2i());
  335. }
  336. void GameView::_play_pressed() {
  337. if (!is_feature_enabled) {
  338. return;
  339. }
  340. OS::ProcessID current_process_id = EditorRunBar::get_singleton()->get_current_process();
  341. if (current_process_id == 0) {
  342. return;
  343. }
  344. if (!window_wrapper->get_window_enabled()) {
  345. screen_index_before_start = EditorNode::get_singleton()->get_editor_main_screen()->get_selected_index();
  346. }
  347. if (embed_on_play && _get_embed_available() == EMBED_AVAILABLE) {
  348. // It's important to disable the low power mode when unfocused because otherwise
  349. // the button in the editor are not responsive and if the user moves the mouse quickly,
  350. // the mouse clicks are not registered.
  351. EditorNode::get_singleton()->set_unfocused_low_processor_usage_mode_enabled(false);
  352. _update_embed_window_size();
  353. if (!window_wrapper->get_window_enabled()) {
  354. EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_GAME);
  355. // Reset the normal size of the bottom panel when fully expanded.
  356. EditorNode::get_singleton()->get_bottom_panel()->set_expanded(false);
  357. embedded_process->grab_focus();
  358. }
  359. embedded_process->embed_process(current_process_id);
  360. _update_ui();
  361. }
  362. }
  363. void GameView::_stop_pressed() {
  364. if (!is_feature_enabled) {
  365. return;
  366. }
  367. _detach_script_debugger();
  368. paused = false;
  369. EditorNode::get_singleton()->set_unfocused_low_processor_usage_mode_enabled(true);
  370. embedded_process->reset();
  371. _update_ui();
  372. if (window_wrapper->get_window_enabled()) {
  373. window_wrapper->set_window_enabled(false);
  374. }
  375. if (screen_index_before_start >= 0 && EditorNode::get_singleton()->get_editor_main_screen()->get_selected_index() == EditorMainScreen::EDITOR_GAME) {
  376. // We go back to the screen where the user was before starting the game.
  377. EditorNode::get_singleton()->get_editor_main_screen()->select(screen_index_before_start);
  378. }
  379. screen_index_before_start = -1;
  380. }
  381. void GameView::_embedding_completed() {
  382. #ifndef MACOS_ENABLED
  383. _attach_script_debugger();
  384. #endif
  385. _update_ui();
  386. if (make_floating_on_play) {
  387. get_window()->set_flag(Window::FLAG_ALWAYS_ON_TOP, bool(GLOBAL_GET("display/window/size/always_on_top")));
  388. }
  389. }
  390. void GameView::_embedding_failed() {
  391. state_label->set_text(TTRC("Connection impossible to the game process."));
  392. }
  393. void GameView::_embedded_process_updated() {
  394. const Rect2i game_rect = embedded_process->get_screen_embedded_window_rect();
  395. game_size_label->set_text(vformat("%dx%d", game_rect.size.x, game_rect.size.y));
  396. }
  397. void GameView::_embedded_process_focused() {
  398. if (embed_on_play && !window_wrapper->get_window_enabled()) {
  399. EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_GAME);
  400. }
  401. }
  402. void GameView::_editor_or_project_settings_changed() {
  403. // Update the window size and aspect ratio.
  404. _update_embed_window_size();
  405. if (window_wrapper->get_window_enabled()) {
  406. _show_update_window_wrapper();
  407. if (embedded_process->is_embedding_completed()) {
  408. embedded_process->queue_update_embedded_process();
  409. }
  410. }
  411. _update_ui();
  412. }
  413. void GameView::_update_debugger_buttons() {
  414. bool empty = active_sessions == 0;
  415. suspend_button->set_disabled(empty);
  416. camera_override_button->set_disabled(empty);
  417. PopupMenu *menu = camera_override_menu->get_popup();
  418. bool disable_camera_reset = empty || !camera_override_button->is_pressed() || !menu->is_item_checked(menu->get_item_index(CAMERA_MODE_INGAME));
  419. menu->set_item_disabled(CAMERA_RESET_2D, disable_camera_reset);
  420. menu->set_item_disabled(CAMERA_RESET_3D, disable_camera_reset);
  421. if (empty) {
  422. suspend_button->set_pressed(false);
  423. camera_override_button->set_pressed(false);
  424. }
  425. next_frame_button->set_disabled(!suspend_button->is_pressed());
  426. }
  427. void GameView::_handle_shortcut_requested(int p_embed_action) {
  428. switch (p_embed_action) {
  429. case ScriptEditorDebugger::EMBED_SUSPEND_TOGGLE: {
  430. _toggle_suspend_button();
  431. } break;
  432. case ScriptEditorDebugger::EMBED_NEXT_FRAME: {
  433. debugger->next_frame();
  434. } break;
  435. }
  436. }
  437. void GameView::_toggle_suspend_button() {
  438. const bool new_pressed = !suspend_button->is_pressed();
  439. suspend_button->set_pressed(new_pressed);
  440. _suspend_button_toggled(new_pressed);
  441. }
  442. void GameView::_suspend_button_toggled(bool p_pressed) {
  443. _update_debugger_buttons();
  444. debugger->set_suspend(p_pressed);
  445. }
  446. void GameView::_node_type_pressed(int p_option) {
  447. RuntimeNodeSelect::NodeType type = (RuntimeNodeSelect::NodeType)p_option;
  448. for (int i = 0; i < RuntimeNodeSelect::NODE_TYPE_MAX; i++) {
  449. node_type_button[i]->set_pressed_no_signal(i == type);
  450. }
  451. _update_debugger_buttons();
  452. debugger->set_node_type(type);
  453. }
  454. void GameView::_select_mode_pressed(int p_option) {
  455. RuntimeNodeSelect::SelectMode mode = (RuntimeNodeSelect::SelectMode)p_option;
  456. if (!select_mode_button[mode]->is_visible()) {
  457. return;
  458. }
  459. for (int i = 0; i < RuntimeNodeSelect::SELECT_MODE_MAX; i++) {
  460. select_mode_button[i]->set_pressed_no_signal(i == mode);
  461. }
  462. debugger->set_select_mode(mode);
  463. EditorSettings::get_singleton()->set_project_metadata("game_view", "select_mode", mode);
  464. }
  465. void GameView::_embed_options_menu_menu_id_pressed(int p_id) {
  466. switch (p_id) {
  467. case EMBED_RUN_GAME_EMBEDDED: {
  468. embed_on_play = !embed_on_play;
  469. int game_mode = EDITOR_GET("run/window_placement/game_embed_mode");
  470. if (game_mode == 0) { // Save only if not overridden by editor.
  471. EditorSettings::get_singleton()->set_project_metadata("game_view", "embed_on_play", embed_on_play);
  472. }
  473. } break;
  474. case EMBED_MAKE_FLOATING_ON_PLAY: {
  475. make_floating_on_play = !make_floating_on_play;
  476. int game_mode = EDITOR_GET("run/window_placement/game_embed_mode");
  477. if (game_mode == 0) { // Save only if not overridden by editor.
  478. EditorSettings::get_singleton()->set_project_metadata("game_view", "make_floating_on_play", make_floating_on_play);
  479. }
  480. } break;
  481. }
  482. _update_embed_menu_options();
  483. _update_ui();
  484. }
  485. void GameView::_size_mode_button_pressed(int size_mode) {
  486. embed_size_mode = (EmbedSizeMode)size_mode;
  487. EditorSettings::get_singleton()->set_project_metadata("game_view", "embed_size_mode", size_mode);
  488. _update_embed_menu_options();
  489. _update_embed_window_size();
  490. }
  491. GameView::EmbedAvailability GameView::_get_embed_available() {
  492. if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) {
  493. return EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED;
  494. }
  495. if (get_tree()->get_root()->is_embedding_subwindows()) {
  496. return EMBED_NOT_AVAILABLE_SINGLE_WINDOW_MODE;
  497. }
  498. String display_driver = GLOBAL_GET("display/display_server/driver");
  499. if (display_driver == "headless" || display_driver == "wayland") {
  500. return EMBED_NOT_AVAILABLE_PROJECT_DISPLAY_DRIVER;
  501. }
  502. EditorRun::WindowPlacement placement = EditorRun::get_window_placement();
  503. if (placement.force_fullscreen) {
  504. return EMBED_NOT_AVAILABLE_FULLSCREEN;
  505. }
  506. if (placement.force_maximized) {
  507. return EMBED_NOT_AVAILABLE_MAXIMIZED;
  508. }
  509. DisplayServer::WindowMode window_mode = (DisplayServer::WindowMode)(GLOBAL_GET("display/window/size/mode").operator int());
  510. if (window_mode == DisplayServer::WindowMode::WINDOW_MODE_MINIMIZED) {
  511. return EMBED_NOT_AVAILABLE_MINIMIZED;
  512. }
  513. if (window_mode == DisplayServer::WindowMode::WINDOW_MODE_MAXIMIZED) {
  514. return EMBED_NOT_AVAILABLE_MAXIMIZED;
  515. }
  516. if (window_mode == DisplayServer::WindowMode::WINDOW_MODE_FULLSCREEN || window_mode == DisplayServer::WindowMode::WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
  517. return EMBED_NOT_AVAILABLE_FULLSCREEN;
  518. }
  519. return EMBED_AVAILABLE;
  520. }
  521. void GameView::_update_ui() {
  522. bool show_game_size = false;
  523. EmbedAvailability available = _get_embed_available();
  524. switch (available) {
  525. case EMBED_AVAILABLE:
  526. if (embedded_process->is_embedding_completed()) {
  527. state_label->set_text("");
  528. show_game_size = true;
  529. } else if (embedded_process->is_embedding_in_progress()) {
  530. state_label->set_text(TTRC("Game starting..."));
  531. } else if (EditorRunBar::get_singleton()->is_playing()) {
  532. state_label->set_text(TTRC("Game running not embedded."));
  533. } else if (embed_on_play) {
  534. state_label->set_text(TTRC("Press play to start the game."));
  535. } else {
  536. state_label->set_text(TTRC("Embedding is disabled."));
  537. }
  538. break;
  539. case EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED:
  540. if (DisplayServer::get_singleton()->get_name() == "Wayland") {
  541. state_label->set_text(TTRC("Game embedding not available on Wayland.\nWayland can be disabled in the Editor Settings (Run > Platforms > Linux/*BSD > Prefer Wayland)."));
  542. } else {
  543. state_label->set_text(TTRC("Game embedding not available on your OS."));
  544. }
  545. break;
  546. case EMBED_NOT_AVAILABLE_PROJECT_DISPLAY_DRIVER:
  547. state_label->set_text(vformat(TTR("Game embedding not available for the Display Server: '%s'.\nDisplay Server can be modified in the Project Settings (Display > Display Server > Driver)."), GLOBAL_GET("display/display_server/driver")));
  548. break;
  549. case EMBED_NOT_AVAILABLE_MINIMIZED:
  550. 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."));
  551. break;
  552. case EMBED_NOT_AVAILABLE_MAXIMIZED:
  553. 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."));
  554. break;
  555. case EMBED_NOT_AVAILABLE_FULLSCREEN:
  556. 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."));
  557. break;
  558. case EMBED_NOT_AVAILABLE_SINGLE_WINDOW_MODE:
  559. state_label->set_text(TTRC("Game embedding not available in single window mode."));
  560. break;
  561. }
  562. if (available == EMBED_AVAILABLE) {
  563. if (state_label->has_theme_color_override(SceneStringName(font_color))) {
  564. state_label->remove_theme_color_override(SceneStringName(font_color));
  565. }
  566. } else {
  567. state_label->add_theme_color_override(SceneStringName(font_color), state_label->get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
  568. }
  569. game_size_label->set_visible(show_game_size);
  570. }
  571. void GameView::_update_embed_menu_options() {
  572. bool is_multi_window = window_wrapper->is_window_available();
  573. PopupMenu *menu = embed_options_menu->get_popup();
  574. menu->set_item_checked(menu->get_item_index(EMBED_RUN_GAME_EMBEDDED), embed_on_play);
  575. menu->set_item_checked(menu->get_item_index(EMBED_MAKE_FLOATING_ON_PLAY), make_floating_on_play && is_multi_window);
  576. menu->set_item_disabled(menu->get_item_index(EMBED_MAKE_FLOATING_ON_PLAY), !embed_on_play || !is_multi_window);
  577. fixed_size_button->set_pressed(embed_size_mode == SIZE_MODE_FIXED);
  578. keep_aspect_button->set_pressed(embed_size_mode == SIZE_MODE_KEEP_ASPECT);
  579. stretch_button->set_pressed(embed_size_mode == SIZE_MODE_STRETCH);
  580. }
  581. void GameView::_update_embed_window_size() {
  582. if (paused) {
  583. // When paused, Godot does not re-render. As a result, resizing the game window to a larger size
  584. // causes artifacts and flickering. However, resizing to a smaller size seems fine.
  585. // To prevent artifacts and flickering, we will force the game window to maintain its size.
  586. // Using the same technique as SIZE_MODE_FIXED, the embedded process control will
  587. // prevent resizing the game to a larger size while maintaining the aspect ratio.
  588. embedded_process->set_window_size(size_paused);
  589. embedded_process->set_keep_aspect(false);
  590. } else {
  591. if (embed_size_mode == SIZE_MODE_FIXED || embed_size_mode == SIZE_MODE_KEEP_ASPECT) {
  592. // The embedded process control will need the desired window size.
  593. EditorRun::WindowPlacement placement = EditorRun::get_window_placement();
  594. embedded_process->set_window_size(placement.size);
  595. } else {
  596. // Stretch... No need for the window size.
  597. embedded_process->set_window_size(Size2i());
  598. }
  599. embedded_process->set_keep_aspect(embed_size_mode == SIZE_MODE_KEEP_ASPECT);
  600. }
  601. }
  602. void GameView::_hide_selection_toggled(bool p_pressed) {
  603. hide_selection->set_button_icon(get_editor_theme_icon(p_pressed ? SNAME("GuiVisibilityHidden") : SNAME("GuiVisibilityVisible")));
  604. debugger->set_selection_visible(!p_pressed);
  605. EditorSettings::get_singleton()->set_project_metadata("game_view", "hide_selection", p_pressed);
  606. }
  607. void GameView::_debug_mute_audio_button_pressed() {
  608. debug_mute_audio = !debug_mute_audio;
  609. debug_mute_audio_button->set_button_icon(get_editor_theme_icon(debug_mute_audio ? SNAME("AudioMute") : SNAME("AudioStreamPlayer")));
  610. debug_mute_audio_button->set_tooltip_text(debug_mute_audio ? TTRC("Unmute game audio.") : TTRC("Mute game audio."));
  611. debugger->set_debug_mute_audio(debug_mute_audio);
  612. }
  613. void GameView::_camera_override_button_toggled(bool p_pressed) {
  614. _update_debugger_buttons();
  615. debugger->set_camera_override(p_pressed);
  616. }
  617. void GameView::_camera_override_menu_id_pressed(int p_id) {
  618. PopupMenu *menu = camera_override_menu->get_popup();
  619. if (p_id != CAMERA_RESET_2D && p_id != CAMERA_RESET_3D) {
  620. for (int i = 0; i < menu->get_item_count(); i++) {
  621. menu->set_item_checked(i, false);
  622. }
  623. }
  624. switch (p_id) {
  625. case CAMERA_RESET_2D: {
  626. debugger->reset_camera_2d_position();
  627. } break;
  628. case CAMERA_RESET_3D: {
  629. debugger->reset_camera_3d_position();
  630. } break;
  631. case CAMERA_MODE_INGAME: {
  632. debugger->set_camera_manipulate_mode(EditorDebuggerNode::OVERRIDE_INGAME);
  633. menu->set_item_checked(menu->get_item_index(p_id), true);
  634. _update_debugger_buttons();
  635. EditorSettings::get_singleton()->set_project_metadata("game_view", "camera_override_mode", p_id);
  636. } break;
  637. case CAMERA_MODE_EDITORS: {
  638. debugger->set_camera_manipulate_mode(EditorDebuggerNode::OVERRIDE_EDITORS);
  639. menu->set_item_checked(menu->get_item_index(p_id), true);
  640. _update_debugger_buttons();
  641. EditorSettings::get_singleton()->set_project_metadata("game_view", "camera_override_mode", p_id);
  642. } break;
  643. }
  644. }
  645. void GameView::_notification(int p_what) {
  646. switch (p_what) {
  647. case NOTIFICATION_TRANSLATION_CHANGED: {
  648. 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."));
  649. _update_ui();
  650. } break;
  651. case NOTIFICATION_THEME_CHANGED: {
  652. suspend_button->set_button_icon(get_editor_theme_icon(SNAME("Pause")));
  653. next_frame_button->set_button_icon(get_editor_theme_icon(SNAME("NextFrame")));
  654. node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->set_button_icon(get_editor_theme_icon(SNAME("InputEventJoypadMotion")));
  655. node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]->set_button_icon(get_editor_theme_icon(SNAME("2DNodes")));
  656. node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->set_button_icon(get_editor_theme_icon(SNAME("Node3D")));
  657. select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_button_icon(get_editor_theme_icon(SNAME("ToolSelect")));
  658. select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_button_icon(get_editor_theme_icon(SNAME("ListSelect")));
  659. hide_selection->set_button_icon(get_editor_theme_icon(hide_selection->is_pressed() ? SNAME("GuiVisibilityHidden") : SNAME("GuiVisibilityVisible")));
  660. fixed_size_button->set_button_icon(get_editor_theme_icon(SNAME("FixedSize")));
  661. keep_aspect_button->set_button_icon(get_editor_theme_icon(SNAME("KeepAspect")));
  662. stretch_button->set_button_icon(get_editor_theme_icon(SNAME("Stretch")));
  663. embed_options_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
  664. debug_mute_audio_button->set_button_icon(get_editor_theme_icon(debug_mute_audio ? SNAME("AudioMute") : SNAME("AudioStreamPlayer")));
  665. camera_override_button->set_button_icon(get_editor_theme_icon(SNAME("Camera")));
  666. camera_override_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
  667. } break;
  668. case NOTIFICATION_READY: {
  669. if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) {
  670. // Embedding available.
  671. int game_mode = EDITOR_GET("run/window_placement/game_embed_mode");
  672. switch (game_mode) {
  673. case -1: { // Disabled.
  674. embed_on_play = false;
  675. make_floating_on_play = false;
  676. } break;
  677. case 1: { // Embed.
  678. embed_on_play = true;
  679. make_floating_on_play = false;
  680. } break;
  681. case 2: { // Floating.
  682. embed_on_play = true;
  683. make_floating_on_play = true;
  684. } break;
  685. default: {
  686. embed_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "embed_on_play", true);
  687. make_floating_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "make_floating_on_play", true);
  688. } break;
  689. }
  690. embed_size_mode = (EmbedSizeMode)(int)EditorSettings::get_singleton()->get_project_metadata("game_view", "embed_size_mode", SIZE_MODE_FIXED);
  691. keep_aspect_button->set_pressed(EditorSettings::get_singleton()->get_project_metadata("game_view", "keep_aspect", true));
  692. _update_embed_menu_options();
  693. EditorRunBar::get_singleton()->connect("play_pressed", callable_mp(this, &GameView::_play_pressed));
  694. EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &GameView::_stop_pressed));
  695. EditorRun::instance_starting_callback = _instance_starting_static;
  696. EditorRun::instance_rq_screenshot_callback = _instance_rq_screenshot_static;
  697. // Listen for project settings changes to update the window size and aspect ratio.
  698. ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &GameView::_editor_or_project_settings_changed));
  699. EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &GameView::_editor_or_project_settings_changed));
  700. } else {
  701. // Embedding not available.
  702. embedding_separator->hide();
  703. embed_options_menu->hide();
  704. fixed_size_button->hide();
  705. keep_aspect_button->hide();
  706. stretch_button->hide();
  707. }
  708. _update_ui();
  709. } break;
  710. case NOTIFICATION_WM_POSITION_CHANGED: {
  711. if (window_wrapper->get_window_enabled()) {
  712. _update_floating_window_settings();
  713. }
  714. } break;
  715. }
  716. }
  717. void GameView::set_window_layout(Ref<ConfigFile> p_layout) {
  718. floating_window_rect = p_layout->get_value("GameView", "floating_window_rect", Rect2i());
  719. floating_window_screen = p_layout->get_value("GameView", "floating_window_screen", -1);
  720. }
  721. void GameView::get_window_layout(Ref<ConfigFile> p_layout) {
  722. if (window_wrapper->get_window_enabled()) {
  723. _update_floating_window_settings();
  724. }
  725. p_layout->set_value("GameView", "floating_window_rect", floating_window_rect);
  726. p_layout->set_value("GameView", "floating_window_screen", floating_window_screen);
  727. }
  728. void GameView::_update_floating_window_settings() {
  729. if (window_wrapper->get_window_enabled()) {
  730. floating_window_rect = window_wrapper->get_window_rect();
  731. floating_window_screen = window_wrapper->get_window_screen();
  732. }
  733. }
  734. void GameView::_attach_script_debugger() {
  735. _detach_script_debugger();
  736. int i = 0;
  737. while (ScriptEditorDebugger *script_debugger = EditorDebuggerNode::get_singleton()->get_debugger(i)) {
  738. if (script_debugger->is_session_active() && script_debugger->get_remote_pid() == embedded_process->get_embedded_pid()) {
  739. embedded_script_debugger = script_debugger;
  740. break;
  741. }
  742. i++;
  743. }
  744. #ifdef MACOS_ENABLED
  745. embedded_process->set_script_debugger(embedded_script_debugger);
  746. #endif
  747. if (embedded_script_debugger) {
  748. embedded_script_debugger->connect("remote_window_title_changed", callable_mp(this, &GameView::_remote_window_title_changed));
  749. embedded_script_debugger->connect("embed_shortcut_requested", callable_mp(this, &GameView::_handle_shortcut_requested));
  750. }
  751. }
  752. void GameView::_detach_script_debugger() {
  753. if (embedded_script_debugger) {
  754. embedded_script_debugger->disconnect("remote_window_title_changed", callable_mp(this, &GameView::_remote_window_title_changed));
  755. embedded_script_debugger->disconnect("embed_shortcut_requested", callable_mp(this, &GameView::_handle_shortcut_requested));
  756. embedded_script_debugger = nullptr;
  757. }
  758. embedded_process->set_script_debugger(nullptr);
  759. }
  760. void GameView::_remote_window_title_changed(String title) {
  761. window_wrapper->set_window_title(title);
  762. }
  763. void GameView::_update_arguments_for_instance(int p_idx, List<String> &r_arguments) {
  764. if (p_idx != 0 || !embed_on_play || _get_embed_available() != EMBED_AVAILABLE) {
  765. return;
  766. }
  767. // Remove duplicates/unwanted parameters.
  768. List<String>::Element *E = r_arguments.front();
  769. List<String>::Element *user_args_element = nullptr;
  770. HashSet<String> remove_args({ "--position", "--resolution", "--screen" });
  771. #ifdef MACOS_ENABLED
  772. // macOS requires the embedded display driver.
  773. remove_args.insert("--display-driver");
  774. #endif
  775. while (E) {
  776. List<String>::Element *N = E->next();
  777. // For these parameters, we need to also remove the value.
  778. if (remove_args.has(E->get())) {
  779. r_arguments.erase(E);
  780. if (N) {
  781. List<String>::Element *V = N->next();
  782. r_arguments.erase(N);
  783. N = V;
  784. }
  785. } else if (E->get() == "-f" || E->get() == "--fullscreen" || E->get() == "-m" || E->get() == "--maximized" || E->get() == "-t" || E->get() == "-always-on-top") {
  786. r_arguments.erase(E);
  787. } else if (E->get() == "--" || E->get() == "++") {
  788. user_args_element = E;
  789. break;
  790. }
  791. E = N;
  792. }
  793. // Add the editor window's native ID so the started game can directly set it as its parent.
  794. List<String>::Element *N = r_arguments.insert_before(user_args_element, "--wid");
  795. N = r_arguments.insert_after(N, itos(DisplayServer::get_singleton()->window_get_native_handle(DisplayServer::WINDOW_HANDLE, get_window()->get_window_id())));
  796. #if MACOS_ENABLED
  797. N = r_arguments.insert_after(N, "--embedded");
  798. #endif
  799. // Be sure to have the correct window size in the embedded_process control.
  800. _update_embed_window_size();
  801. Rect2i rect = embedded_process->get_screen_embedded_window_rect();
  802. // Usually, the global rect of the embedded process control is invalid because it was hidden. We will calculate it manually.
  803. if (!window_wrapper->get_window_enabled()) {
  804. Size2 old_min_size = embedded_process->get_custom_minimum_size();
  805. embedded_process->set_custom_minimum_size(Size2i());
  806. Control *container = EditorNode::get_singleton()->get_editor_main_screen()->get_control();
  807. rect = container->get_global_rect();
  808. Size2 wrapped_min_size = window_wrapper->get_minimum_size();
  809. rect.position.y += wrapped_min_size.y;
  810. rect.size.y -= wrapped_min_size.y;
  811. rect = embedded_process->get_adjusted_embedded_window_rect(rect);
  812. embedded_process->set_custom_minimum_size(old_min_size);
  813. }
  814. // When using the floating window, we need to force the position and size from the
  815. // editor/project settings, because the get_screen_embedded_window_rect of the
  816. // embedded_process will be updated only on the next frame.
  817. if (window_wrapper->get_window_enabled()) {
  818. EditorRun::WindowPlacement placement = EditorRun::get_window_placement();
  819. if (placement.position != Point2i(INT_MAX, INT_MAX)) {
  820. rect.position = placement.position;
  821. }
  822. if (placement.size != Size2i()) {
  823. rect.size = placement.size;
  824. }
  825. }
  826. N = r_arguments.insert_after(N, "--position");
  827. N = r_arguments.insert_after(N, itos(rect.position.x) + "," + itos(rect.position.y));
  828. N = r_arguments.insert_after(N, "--resolution");
  829. r_arguments.insert_after(N, itos(rect.size.x) + "x" + itos(rect.size.y));
  830. }
  831. void GameView::_window_close_request() {
  832. if (window_wrapper->get_window_enabled()) {
  833. window_wrapper->set_window_enabled(false);
  834. }
  835. // Before the parent window closed, we close the embedded game. That prevents
  836. // the embedded game to be seen without a parent window for a fraction of second.
  837. if (EditorRunBar::get_singleton()->is_playing() && (embedded_process->is_embedding_completed() || embedded_process->is_embedding_in_progress())) {
  838. // When the embedding is not complete, we need to kill the process.
  839. // If the game is paused, the close request will not be processed by the game, so it's better to kill the process.
  840. if (paused || embedded_process->is_embedding_in_progress()) {
  841. embedded_process->reset();
  842. // Call deferred to prevent the _stop_pressed callback to be executed before the wrapper window
  843. // actually closes.
  844. callable_mp(EditorRunBar::get_singleton(), &EditorRunBar::stop_playing).call_deferred();
  845. } else {
  846. // Try to gracefully close the window. That way, the NOTIFICATION_WM_CLOSE_REQUEST
  847. // notification should be propagated in the game process.
  848. embedded_process->request_close();
  849. }
  850. }
  851. }
  852. void GameView::_debugger_breaked(bool p_breaked, bool p_can_debug) {
  853. if (p_breaked == paused) {
  854. return;
  855. }
  856. paused = p_breaked;
  857. if (paused) {
  858. size_paused = embedded_process->get_screen_embedded_window_rect().size;
  859. }
  860. _update_embed_window_size();
  861. }
  862. void GameView::_feature_profile_changed() {
  863. Ref<EditorFeatureProfile> profile = EditorFeatureProfileManager::get_singleton()->get_current_profile();
  864. bool is_profile_null = profile.is_null();
  865. is_feature_enabled = is_profile_null || !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_GAME);
  866. bool is_3d_enabled = is_profile_null || !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D);
  867. if (!is_3d_enabled && node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->is_pressed()) {
  868. _node_type_pressed(RuntimeNodeSelect::NODE_TYPE_NONE);
  869. }
  870. node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->set_visible(is_3d_enabled);
  871. }
  872. GameView::GameView(Ref<GameViewDebugger> p_debugger, EmbeddedProcessBase *p_embedded_process, WindowWrapper *p_wrapper) {
  873. singleton = this;
  874. debugger = p_debugger;
  875. window_wrapper = p_wrapper;
  876. embedded_process = p_embedded_process;
  877. // Add some margin to the sides for better aesthetics.
  878. // This prevents the first button's hover/pressed effect from "touching" the panel's border,
  879. // which looks ugly.
  880. MarginContainer *toolbar_margin = memnew(MarginContainer);
  881. toolbar_margin->add_theme_constant_override("margin_left", 4 * EDSCALE);
  882. toolbar_margin->add_theme_constant_override("margin_right", 4 * EDSCALE);
  883. add_child(toolbar_margin);
  884. HBoxContainer *main_menu_hbox = memnew(HBoxContainer);
  885. toolbar_margin->add_child(main_menu_hbox);
  886. suspend_button = memnew(Button);
  887. main_menu_hbox->add_child(suspend_button);
  888. suspend_button->set_toggle_mode(true);
  889. suspend_button->set_theme_type_variation(SceneStringName(FlatButton));
  890. suspend_button->connect(SceneStringName(toggled), callable_mp(this, &GameView::_suspend_button_toggled));
  891. suspend_button->set_accessibility_name(TTRC("Suspend"));
  892. suspend_button->set_shortcut(ED_GET_SHORTCUT("editor/suspend_resume_embedded_project"));
  893. next_frame_button = memnew(Button);
  894. main_menu_hbox->add_child(next_frame_button);
  895. next_frame_button->set_theme_type_variation(SceneStringName(FlatButton));
  896. next_frame_button->connect(SceneStringName(pressed), callable_mp(*debugger, &GameViewDebugger::next_frame));
  897. next_frame_button->set_accessibility_name(TTRC("Next Frame"));
  898. next_frame_button->set_shortcut(ED_GET_SHORTCUT("editor/next_frame_embedded_project"));
  899. main_menu_hbox->add_child(memnew(VSeparator));
  900. node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE] = memnew(Button);
  901. main_menu_hbox->add_child(node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]);
  902. node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->set_text(TTRC("Input"));
  903. node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->set_toggle_mode(true);
  904. node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->set_pressed(true);
  905. node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->set_theme_type_variation(SceneStringName(FlatButton));
  906. node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->connect(SceneStringName(pressed), callable_mp(this, &GameView::_node_type_pressed).bind(RuntimeNodeSelect::NODE_TYPE_NONE));
  907. node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->set_tooltip_text(TTRC("Allow game input."));
  908. node_type_button[RuntimeNodeSelect::NODE_TYPE_2D] = memnew(Button);
  909. main_menu_hbox->add_child(node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]);
  910. node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]->set_text(TTRC("2D"));
  911. node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]->set_toggle_mode(true);
  912. node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]->set_theme_type_variation(SceneStringName(FlatButton));
  913. node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]->connect(SceneStringName(pressed), callable_mp(this, &GameView::_node_type_pressed).bind(RuntimeNodeSelect::NODE_TYPE_2D));
  914. 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."));
  915. node_type_button[RuntimeNodeSelect::NODE_TYPE_3D] = memnew(Button);
  916. main_menu_hbox->add_child(node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]);
  917. node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->set_text(TTRC("3D"));
  918. node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->set_toggle_mode(true);
  919. node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->set_theme_type_variation(SceneStringName(FlatButton));
  920. node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->connect(SceneStringName(pressed), callable_mp(this, &GameView::_node_type_pressed).bind(RuntimeNodeSelect::NODE_TYPE_3D));
  921. node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->set_tooltip_text(TTRC("Disable game input and allow to select Node3Ds and manipulate the 3D camera."));
  922. main_menu_hbox->add_child(memnew(VSeparator));
  923. hide_selection = memnew(Button);
  924. main_menu_hbox->add_child(hide_selection);
  925. hide_selection->set_toggle_mode(true);
  926. hide_selection->set_theme_type_variation(SceneStringName(FlatButton));
  927. hide_selection->connect(SceneStringName(toggled), callable_mp(this, &GameView::_hide_selection_toggled));
  928. hide_selection->set_tooltip_text(TTRC("Toggle Selection Visibility"));
  929. hide_selection->set_pressed(EditorSettings::get_singleton()->get_project_metadata("game_view", "hide_selection", false));
  930. main_menu_hbox->add_child(memnew(VSeparator));
  931. select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE] = memnew(Button);
  932. main_menu_hbox->add_child(select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]);
  933. select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_toggle_mode(true);
  934. select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_pressed(true);
  935. select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_theme_type_variation(SceneStringName(FlatButton));
  936. select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->connect(SceneStringName(pressed), callable_mp(this, &GameView::_select_mode_pressed).bind(RuntimeNodeSelect::SELECT_MODE_SINGLE));
  937. select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_shortcut(ED_GET_SHORTCUT("spatial_editor/tool_select"));
  938. select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_shortcut_context(this);
  939. select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST] = memnew(Button);
  940. main_menu_hbox->add_child(select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]);
  941. select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_toggle_mode(true);
  942. select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_theme_type_variation(SceneStringName(FlatButton));
  943. select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->connect(SceneStringName(pressed), callable_mp(this, &GameView::_select_mode_pressed).bind(RuntimeNodeSelect::SELECT_MODE_LIST));
  944. select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_tooltip_text(TTRC("Show list of selectable nodes at position clicked."));
  945. _select_mode_pressed(EditorSettings::get_singleton()->get_project_metadata("game_view", "select_mode", 0));
  946. main_menu_hbox->add_child(memnew(VSeparator));
  947. debug_mute_audio_button = memnew(Button);
  948. main_menu_hbox->add_child(debug_mute_audio_button);
  949. debug_mute_audio_button->set_theme_type_variation("FlatButton");
  950. debug_mute_audio_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_debug_mute_audio_button_pressed));
  951. debug_mute_audio_button->set_tooltip_text(debug_mute_audio ? TTRC("Unmute game audio.") : TTRC("Mute game audio."));
  952. main_menu_hbox->add_child(memnew(VSeparator));
  953. camera_override_button = memnew(Button);
  954. main_menu_hbox->add_child(camera_override_button);
  955. camera_override_button->set_toggle_mode(true);
  956. camera_override_button->set_theme_type_variation(SceneStringName(FlatButton));
  957. camera_override_button->set_tooltip_text(TTRC("Override the in-game camera."));
  958. camera_override_button->connect(SceneStringName(toggled), callable_mp(this, &GameView::_camera_override_button_toggled));
  959. camera_override_menu = memnew(MenuButton);
  960. main_menu_hbox->add_child(camera_override_menu);
  961. camera_override_menu->set_flat(false);
  962. camera_override_menu->set_theme_type_variation("FlatMenuButton");
  963. camera_override_menu->set_h_size_flags(SIZE_SHRINK_END);
  964. camera_override_menu->set_tooltip_text(TTRC("Camera Override Options"));
  965. PopupMenu *menu = camera_override_menu->get_popup();
  966. menu->connect(SceneStringName(id_pressed), callable_mp(this, &GameView::_camera_override_menu_id_pressed));
  967. menu->add_item(TTRC("Reset 2D Camera"), CAMERA_RESET_2D);
  968. menu->add_item(TTRC("Reset 3D Camera"), CAMERA_RESET_3D);
  969. menu->add_separator();
  970. menu->add_radio_check_item(TTRC("Manipulate In-Game"), CAMERA_MODE_INGAME);
  971. menu->set_item_checked(menu->get_item_index(CAMERA_MODE_INGAME), true);
  972. menu->add_radio_check_item(TTRC("Manipulate From Editors"), CAMERA_MODE_EDITORS);
  973. _camera_override_menu_id_pressed(EditorSettings::get_singleton()->get_project_metadata("game_view", "camera_override_mode", 0));
  974. embedding_separator = memnew(VSeparator);
  975. main_menu_hbox->add_child(embedding_separator);
  976. fixed_size_button = memnew(Button);
  977. main_menu_hbox->add_child(fixed_size_button);
  978. fixed_size_button->set_toggle_mode(true);
  979. fixed_size_button->set_theme_type_variation("FlatButton");
  980. fixed_size_button->set_tooltip_text(TTRC("Embedded game size is based on project settings.\nThe 'Keep Aspect' mode is used when the Game Workspace is smaller than the desired size."));
  981. fixed_size_button->set_accessibility_name(TTRC("Embedded game size is based on project settings."));
  982. fixed_size_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_size_mode_button_pressed).bind(SIZE_MODE_FIXED));
  983. keep_aspect_button = memnew(Button);
  984. main_menu_hbox->add_child(keep_aspect_button);
  985. keep_aspect_button->set_toggle_mode(true);
  986. keep_aspect_button->set_theme_type_variation("FlatButton");
  987. keep_aspect_button->set_tooltip_text(TTRC("Keep the aspect ratio of the embedded game."));
  988. keep_aspect_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_size_mode_button_pressed).bind(SIZE_MODE_KEEP_ASPECT));
  989. stretch_button = memnew(Button);
  990. main_menu_hbox->add_child(stretch_button);
  991. stretch_button->set_toggle_mode(true);
  992. stretch_button->set_theme_type_variation("FlatButton");
  993. stretch_button->set_tooltip_text(TTRC("Embedded game size stretches to fit the Game Workspace."));
  994. stretch_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_size_mode_button_pressed).bind(SIZE_MODE_STRETCH));
  995. embed_options_menu = memnew(MenuButton);
  996. main_menu_hbox->add_child(embed_options_menu);
  997. embed_options_menu->set_flat(false);
  998. embed_options_menu->set_theme_type_variation("FlatMenuButton");
  999. embed_options_menu->set_h_size_flags(SIZE_SHRINK_END);
  1000. embed_options_menu->set_tooltip_text(TTRC("Embedding Options"));
  1001. menu = embed_options_menu->get_popup();
  1002. menu->connect(SceneStringName(id_pressed), callable_mp(this, &GameView::_embed_options_menu_menu_id_pressed));
  1003. menu->add_check_item(TTRC("Embed Game on Next Play"), EMBED_RUN_GAME_EMBEDDED);
  1004. menu->add_check_item(TTRC("Make Game Workspace Floating on Next Play"), EMBED_MAKE_FLOATING_ON_PLAY);
  1005. main_menu_hbox->add_spacer();
  1006. game_size_label = memnew(Label());
  1007. main_menu_hbox->add_child(game_size_label);
  1008. game_size_label->hide();
  1009. // Setting the minimum size prevents the game workspace from resizing indefinitely
  1010. // due to the label size oscillating by a few pixels when the game is in stretch mode
  1011. // and the game workspace is at its minimum size.
  1012. game_size_label->set_custom_minimum_size(Size2(80 * EDSCALE, 0));
  1013. game_size_label->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_RIGHT);
  1014. panel = memnew(Panel);
  1015. add_child(panel);
  1016. panel->set_theme_type_variation("GamePanel");
  1017. panel->set_v_size_flags(SIZE_EXPAND_FILL);
  1018. panel->add_child(embedded_process);
  1019. embedded_process->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
  1020. embedded_process->connect("embedding_failed", callable_mp(this, &GameView::_embedding_failed));
  1021. embedded_process->connect("embedding_completed", callable_mp(this, &GameView::_embedding_completed));
  1022. embedded_process->connect("embedded_process_updated", callable_mp(this, &GameView::_embedded_process_updated));
  1023. embedded_process->connect("embedded_process_focused", callable_mp(this, &GameView::_embedded_process_focused));
  1024. embedded_process->set_custom_minimum_size(Size2i(100, 100));
  1025. MarginContainer *state_container = memnew(MarginContainer);
  1026. state_container->add_theme_constant_override("margin_left", 8 * EDSCALE);
  1027. state_container->add_theme_constant_override("margin_right", 8 * EDSCALE);
  1028. state_container->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
  1029. #ifdef MACOS_ENABLED
  1030. state_container->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
  1031. #endif
  1032. panel->add_child(state_container);
  1033. state_label = memnew(Label());
  1034. state_container->add_child(state_label);
  1035. state_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
  1036. state_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
  1037. state_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD);
  1038. state_label->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
  1039. _update_debugger_buttons();
  1040. p_debugger->connect("session_started", callable_mp(this, &GameView::_sessions_changed));
  1041. p_debugger->connect("session_stopped", callable_mp(this, &GameView::_sessions_changed));
  1042. p_wrapper->set_override_close_request(true);
  1043. p_wrapper->connect("window_close_requested", callable_mp(this, &GameView::_window_close_request));
  1044. p_wrapper->connect("window_size_changed", callable_mp(this, &GameView::_update_floating_window_settings));
  1045. EditorDebuggerNode::get_singleton()->connect("breaked", callable_mp(this, &GameView::_debugger_breaked));
  1046. EditorFeatureProfileManager::get_singleton()->connect("current_feature_profile_changed", callable_mp(this, &GameView::_feature_profile_changed));
  1047. }
  1048. ///////
  1049. void GameViewPluginBase::selected_notify() {
  1050. if (_is_window_wrapper_enabled()) {
  1051. #ifdef ANDROID_ENABLED
  1052. notify_main_screen_changed(get_plugin_name());
  1053. #else
  1054. window_wrapper->grab_window_focus();
  1055. #endif // ANDROID_ENABLED
  1056. _focus_another_editor();
  1057. }
  1058. }
  1059. #ifndef ANDROID_ENABLED
  1060. void GameViewPluginBase::make_visible(bool p_visible) {
  1061. if (p_visible) {
  1062. window_wrapper->show();
  1063. } else {
  1064. window_wrapper->hide();
  1065. }
  1066. }
  1067. void GameViewPluginBase::set_window_layout(Ref<ConfigFile> p_layout) {
  1068. game_view->set_window_layout(p_layout);
  1069. }
  1070. void GameViewPluginBase::get_window_layout(Ref<ConfigFile> p_layout) {
  1071. game_view->get_window_layout(p_layout);
  1072. }
  1073. void GameViewPluginBase::setup(Ref<GameViewDebugger> p_debugger, EmbeddedProcessBase *p_embedded_process) {
  1074. debugger = p_debugger;
  1075. window_wrapper = memnew(WindowWrapper);
  1076. window_wrapper->set_margins_enabled(true);
  1077. game_view = memnew(GameView(debugger, p_embedded_process, window_wrapper));
  1078. game_view->set_v_size_flags(Control::SIZE_EXPAND_FILL);
  1079. window_wrapper->set_wrapped_control(game_view, nullptr);
  1080. EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(window_wrapper);
  1081. window_wrapper->set_v_size_flags(Control::SIZE_EXPAND_FILL);
  1082. window_wrapper->hide();
  1083. window_wrapper->connect("window_visibility_changed", callable_mp(this, &GameViewPlugin::_focus_another_editor).unbind(1));
  1084. }
  1085. #endif // ANDROID_ENABLED
  1086. void GameViewPluginBase::_notification(int p_what) {
  1087. switch (p_what) {
  1088. case NOTIFICATION_TRANSLATION_CHANGED: {
  1089. #ifndef ANDROID_ENABLED
  1090. window_wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), TTR("Game Workspace")));
  1091. #endif
  1092. } break;
  1093. case NOTIFICATION_ENTER_TREE: {
  1094. add_debugger_plugin(debugger);
  1095. connect("main_screen_changed", callable_mp(this, &GameViewPluginBase::_save_last_editor));
  1096. } break;
  1097. case NOTIFICATION_EXIT_TREE: {
  1098. remove_debugger_plugin(debugger);
  1099. disconnect("main_screen_changed", callable_mp(this, &GameViewPluginBase::_save_last_editor));
  1100. } break;
  1101. }
  1102. }
  1103. void GameViewPluginBase::_save_last_editor(const String &p_editor) {
  1104. if (p_editor != get_plugin_name()) {
  1105. last_editor = p_editor;
  1106. }
  1107. }
  1108. void GameViewPluginBase::_focus_another_editor() {
  1109. if (_is_window_wrapper_enabled()) {
  1110. if (last_editor.is_empty()) {
  1111. EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_2D);
  1112. } else {
  1113. EditorInterface::get_singleton()->set_main_screen_editor(last_editor);
  1114. }
  1115. }
  1116. }
  1117. bool GameViewPluginBase::_is_window_wrapper_enabled() const {
  1118. #ifdef ANDROID_ENABLED
  1119. return true;
  1120. #else
  1121. return window_wrapper->get_window_enabled();
  1122. #endif // ANDROID_ENABLED
  1123. }
  1124. GameViewPluginBase::GameViewPluginBase() {
  1125. #ifdef ANDROID_ENABLED
  1126. debugger.instantiate();
  1127. #endif
  1128. }
  1129. GameViewPlugin::GameViewPlugin() :
  1130. GameViewPluginBase() {
  1131. #ifndef ANDROID_ENABLED
  1132. Ref<GameViewDebugger> game_view_debugger;
  1133. game_view_debugger.instantiate();
  1134. EmbeddedProcess *embedded_process = memnew(EmbeddedProcess);
  1135. setup(game_view_debugger, embedded_process);
  1136. #endif
  1137. }