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