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