1/**************************************************************************/
2/* editor_run_bar.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 "editor_run_bar.h"
32
33#include "core/config/project_settings.h"
34#include "editor/debugger/editor_debugger_node.h"
35#include "editor/editor_command_palette.h"
36#include "editor/editor_node.h"
37#include "editor/editor_quick_open.h"
38#include "editor/editor_run_native.h"
39#include "editor/editor_settings.h"
40#include "editor/editor_string_names.h"
41#include "scene/gui/box_container.h"
42#include "scene/gui/button.h"
43#include "scene/gui/panel_container.h"
44
45EditorRunBar *EditorRunBar::singleton = nullptr;
46
47void EditorRunBar::_notification(int p_what) {
48 switch (p_what) {
49 case NOTIFICATION_POSTINITIALIZE: {
50 _reset_play_buttons();
51 } break;
52
53 case NOTIFICATION_THEME_CHANGED: {
54 _update_play_buttons();
55 pause_button->set_icon(get_editor_theme_icon(SNAME("Pause")));
56 stop_button->set_icon(get_editor_theme_icon(SNAME("Stop")));
57
58 if (is_movie_maker_enabled()) {
59 main_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("LaunchPadMovieMode"), EditorStringName(EditorStyles)));
60 write_movie_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("MovieWriterButtonPressed"), EditorStringName(EditorStyles)));
61 } else {
62 main_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("LaunchPadNormal"), EditorStringName(EditorStyles)));
63 write_movie_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("MovieWriterButtonNormal"), EditorStringName(EditorStyles)));
64 }
65
66 write_movie_button->set_icon(get_editor_theme_icon(SNAME("MainMovieWrite")));
67 // This button behaves differently, so color it as such.
68 write_movie_button->add_theme_color_override("icon_normal_color", Color(1, 1, 1, 0.7));
69 write_movie_button->add_theme_color_override("icon_pressed_color", Color(0, 0, 0, 0.84));
70 write_movie_button->add_theme_color_override("icon_hover_color", Color(1, 1, 1, 0.9));
71 } break;
72 }
73}
74
75void EditorRunBar::_reset_play_buttons() {
76 play_button->set_pressed(false);
77 play_button->set_icon(get_editor_theme_icon(SNAME("MainPlay")));
78 play_button->set_tooltip_text(TTR("Play the project."));
79
80 play_scene_button->set_pressed(false);
81 play_scene_button->set_icon(get_editor_theme_icon(SNAME("PlayScene")));
82 play_scene_button->set_tooltip_text(TTR("Play the edited scene."));
83
84 play_custom_scene_button->set_pressed(false);
85 play_custom_scene_button->set_icon(get_editor_theme_icon(SNAME("PlayCustom")));
86 play_custom_scene_button->set_tooltip_text(TTR("Play a custom scene."));
87}
88
89void EditorRunBar::_update_play_buttons() {
90 _reset_play_buttons();
91 if (!is_playing()) {
92 return;
93 }
94
95 Button *active_button = nullptr;
96 if (current_mode == RUN_CURRENT) {
97 active_button = play_scene_button;
98 } else if (current_mode == RUN_CUSTOM) {
99 active_button = play_custom_scene_button;
100 } else {
101 active_button = play_button;
102 }
103
104 if (active_button) {
105 active_button->set_pressed(true);
106 active_button->set_icon(get_editor_theme_icon(SNAME("Reload")));
107 active_button->set_tooltip_text(TTR("Reload the played scene."));
108 }
109}
110
111void EditorRunBar::_write_movie_toggled(bool p_enabled) {
112 if (p_enabled) {
113 add_theme_style_override("panel", get_theme_stylebox(SNAME("LaunchPadMovieMode"), EditorStringName(EditorStyles)));
114 write_movie_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("MovieWriterButtonPressed"), EditorStringName(EditorStyles)));
115 } else {
116 add_theme_style_override("panel", get_theme_stylebox(SNAME("LaunchPadNormal"), EditorStringName(EditorStyles)));
117 write_movie_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("MovieWriterButtonNormal"), EditorStringName(EditorStyles)));
118 }
119}
120
121void EditorRunBar::_quick_run_selected() {
122 play_custom_scene(quick_run->get_selected());
123}
124
125void EditorRunBar::_play_custom_pressed() {
126 if (editor_run.get_status() == EditorRun::STATUS_STOP || current_mode != RunMode::RUN_CUSTOM) {
127 stop_playing();
128
129 quick_run->popup_dialog("PackedScene", true);
130 quick_run->set_title(TTR("Quick Run Scene..."));
131 play_custom_scene_button->set_pressed(false);
132 } else {
133 // Reload if already running a custom scene.
134 String last_custom_scene = run_custom_filename; // This is necessary to have a copy of the string.
135 play_custom_scene(last_custom_scene);
136 }
137}
138
139void EditorRunBar::_play_current_pressed() {
140 if (editor_run.get_status() == EditorRun::STATUS_STOP || current_mode != RunMode::RUN_CURRENT) {
141 play_current_scene();
142 } else {
143 // Reload if already running the current scene.
144 play_current_scene(true);
145 }
146}
147
148void EditorRunBar::_run_scene(const String &p_scene_path) {
149 ERR_FAIL_COND_MSG(current_mode == RUN_CUSTOM && p_scene_path.is_empty(), "Attempting to run a custom scene with an empty path.");
150
151 if (editor_run.get_status() == EditorRun::STATUS_PLAY) {
152 return;
153 }
154
155 _reset_play_buttons();
156
157 String write_movie_file;
158 if (is_movie_maker_enabled()) {
159 if (current_mode == RUN_CURRENT) {
160 Node *scene_root = nullptr;
161 if (p_scene_path.is_empty()) {
162 scene_root = get_tree()->get_edited_scene_root();
163 } else {
164 int scene_index = EditorNode::get_editor_data().get_edited_scene_from_path(p_scene_path);
165 if (scene_index >= 0) {
166 scene_root = EditorNode::get_editor_data().get_edited_scene_root(scene_index);
167 }
168 }
169
170 if (scene_root && scene_root->has_meta("movie_file")) {
171 // If the scene file has a movie_file metadata set, use this as file.
172 // Quick workaround if you want to have multiple scenes that write to
173 // multiple movies.
174 write_movie_file = scene_root->get_meta("movie_file");
175 }
176 }
177
178 if (write_movie_file.is_empty()) {
179 write_movie_file = GLOBAL_GET("editor/movie_writer/movie_file");
180 }
181
182 if (write_movie_file.is_empty()) {
183 // TODO: Provide options to directly resolve the issue with a custom dialog.
184 EditorNode::get_singleton()->show_accept(TTR("Movie Maker mode is enabled, but no movie file path has been specified.\nA default movie file path can be specified in the project settings under the Editor > Movie Writer category.\nAlternatively, for running single scenes, a `movie_file` string metadata can be added to the root node,\nspecifying the path to a movie file that will be used when recording that scene."), TTR("OK"));
185 return;
186 }
187 }
188
189 String run_filename;
190 switch (current_mode) {
191 case RUN_CUSTOM: {
192 run_filename = p_scene_path;
193 run_custom_filename = run_filename;
194 } break;
195
196 case RUN_CURRENT: {
197 if (!p_scene_path.is_empty()) {
198 run_filename = p_scene_path;
199 run_current_filename = run_filename;
200 break;
201 }
202
203 Node *scene_root = get_tree()->get_edited_scene_root();
204 if (!scene_root) {
205 EditorNode::get_singleton()->show_accept(TTR("There is no defined scene to run."), TTR("OK"));
206 return;
207 }
208
209 if (scene_root->get_scene_file_path().is_empty()) {
210 EditorNode::get_singleton()->save_before_run();
211 return;
212 }
213
214 run_filename = scene_root->get_scene_file_path();
215 run_current_filename = run_filename;
216 } break;
217
218 default: {
219 if (!EditorNode::get_singleton()->ensure_main_scene(false)) {
220 return;
221 }
222
223 run_filename = GLOBAL_GET("application/run/main_scene");
224 } break;
225 }
226
227 EditorNode::get_singleton()->try_autosave();
228 if (!EditorNode::get_singleton()->call_build()) {
229 return;
230 }
231
232 EditorDebuggerNode::get_singleton()->start();
233 Error error = editor_run.run(run_filename, write_movie_file);
234 if (error != OK) {
235 EditorDebuggerNode::get_singleton()->stop();
236 EditorNode::get_singleton()->show_accept(TTR("Could not start subprocess(es)!"), TTR("OK"));
237 return;
238 }
239
240 _update_play_buttons();
241 stop_button->set_disabled(false);
242
243 emit_signal(SNAME("play_pressed"));
244}
245
246void EditorRunBar::_run_native(const Ref<EditorExportPreset> &p_preset) {
247 EditorNode::get_singleton()->try_autosave();
248
249 if (run_native->is_deploy_debug_remote_enabled()) {
250 stop_playing();
251
252 if (!EditorNode::get_singleton()->call_build()) {
253 return; // Build failed.
254 }
255
256 EditorDebuggerNode::get_singleton()->start(p_preset->get_platform()->get_debug_protocol());
257 emit_signal(SNAME("play_pressed"));
258 editor_run.run_native_notify();
259 }
260}
261
262void EditorRunBar::play_main_scene(bool p_from_native) {
263 if (p_from_native) {
264 run_native->resume_run_native();
265 } else {
266 stop_playing();
267
268 current_mode = RunMode::RUN_MAIN;
269 _run_scene();
270 }
271}
272
273void EditorRunBar::play_current_scene(bool p_reload) {
274 EditorNode::get_singleton()->save_default_environment();
275 stop_playing();
276
277 current_mode = RunMode::RUN_CURRENT;
278 if (p_reload) {
279 String last_current_scene = run_current_filename; // This is necessary to have a copy of the string.
280 _run_scene(last_current_scene);
281 } else {
282 _run_scene();
283 }
284}
285
286void EditorRunBar::play_custom_scene(const String &p_custom) {
287 stop_playing();
288
289 current_mode = RunMode::RUN_CUSTOM;
290 _run_scene(p_custom);
291}
292
293void EditorRunBar::stop_playing() {
294 if (editor_run.get_status() == EditorRun::STATUS_STOP) {
295 return;
296 }
297
298 current_mode = RunMode::STOPPED;
299 editor_run.stop();
300 EditorDebuggerNode::get_singleton()->stop();
301
302 run_custom_filename.clear();
303 run_current_filename.clear();
304 stop_button->set_pressed(false);
305 stop_button->set_disabled(true);
306 _reset_play_buttons();
307
308 emit_signal(SNAME("stop_pressed"));
309}
310
311bool EditorRunBar::is_playing() const {
312 EditorRun::Status status = editor_run.get_status();
313 return (status == EditorRun::STATUS_PLAY || status == EditorRun::STATUS_PAUSED);
314}
315
316String EditorRunBar::get_playing_scene() const {
317 String run_filename = editor_run.get_running_scene();
318 if (run_filename.is_empty() && is_playing()) {
319 run_filename = GLOBAL_GET("application/run/main_scene"); // Must be the main scene then.
320 }
321
322 return run_filename;
323}
324
325Error EditorRunBar::start_native_device(int p_device_id) {
326 return run_native->start_run_native(p_device_id);
327}
328
329OS::ProcessID EditorRunBar::has_child_process(OS::ProcessID p_pid) const {
330 return editor_run.has_child_process(p_pid);
331}
332
333void EditorRunBar::stop_child_process(OS::ProcessID p_pid) {
334 if (!has_child_process(p_pid)) {
335 return;
336 }
337
338 editor_run.stop_child_process(p_pid);
339 if (!editor_run.get_child_process_count()) { // All children stopped. Closing.
340 stop_playing();
341 }
342}
343
344void EditorRunBar::set_movie_maker_enabled(bool p_enabled) {
345 write_movie_button->set_pressed(p_enabled);
346}
347
348bool EditorRunBar::is_movie_maker_enabled() const {
349 return write_movie_button->is_pressed();
350}
351
352HBoxContainer *EditorRunBar::get_buttons_container() {
353 return main_hbox;
354}
355
356void EditorRunBar::_bind_methods() {
357 ADD_SIGNAL(MethodInfo("play_pressed"));
358 ADD_SIGNAL(MethodInfo("stop_pressed"));
359}
360
361EditorRunBar::EditorRunBar() {
362 singleton = this;
363
364 main_panel = memnew(PanelContainer);
365 add_child(main_panel);
366
367 main_hbox = memnew(HBoxContainer);
368 main_panel->add_child(main_hbox);
369
370 play_button = memnew(Button);
371 main_hbox->add_child(play_button);
372 play_button->set_flat(true);
373 play_button->set_toggle_mode(true);
374 play_button->set_focus_mode(Control::FOCUS_NONE);
375 play_button->set_tooltip_text(TTR("Run the project's default scene."));
376 play_button->connect("pressed", callable_mp(this, &EditorRunBar::play_main_scene).bind(false));
377
378 ED_SHORTCUT_AND_COMMAND("editor/run_project", TTR("Run Project"), Key::F5);
379 ED_SHORTCUT_OVERRIDE("editor/run_project", "macos", KeyModifierMask::META | Key::B);
380 play_button->set_shortcut(ED_GET_SHORTCUT("editor/run_project"));
381
382 pause_button = memnew(Button);
383 main_hbox->add_child(pause_button);
384 pause_button->set_flat(true);
385 pause_button->set_toggle_mode(true);
386 pause_button->set_focus_mode(Control::FOCUS_NONE);
387 pause_button->set_tooltip_text(TTR("Pause the running project's execution for debugging."));
388 pause_button->set_disabled(true);
389
390 ED_SHORTCUT("editor/pause_running_project", TTR("Pause Running Project"), Key::F7);
391 ED_SHORTCUT_OVERRIDE("editor/pause_running_project", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::Y);
392 pause_button->set_shortcut(ED_GET_SHORTCUT("editor/pause_running_project"));
393
394 stop_button = memnew(Button);
395 main_hbox->add_child(stop_button);
396 stop_button->set_flat(true);
397 stop_button->set_focus_mode(Control::FOCUS_NONE);
398 stop_button->set_tooltip_text(TTR("Stop the currently running project."));
399 stop_button->set_disabled(true);
400 stop_button->connect("pressed", callable_mp(this, &EditorRunBar::stop_playing));
401
402 ED_SHORTCUT("editor/stop_running_project", TTR("Stop Running Project"), Key::F8);
403 ED_SHORTCUT_OVERRIDE("editor/stop_running_project", "macos", KeyModifierMask::META | Key::PERIOD);
404 stop_button->set_shortcut(ED_GET_SHORTCUT("editor/stop_running_project"));
405
406 run_native = memnew(EditorRunNative);
407 main_hbox->add_child(run_native);
408 run_native->connect("native_run", callable_mp(this, &EditorRunBar::_run_native));
409
410 play_scene_button = memnew(Button);
411 main_hbox->add_child(play_scene_button);
412 play_scene_button->set_flat(true);
413 play_scene_button->set_toggle_mode(true);
414 play_scene_button->set_focus_mode(Control::FOCUS_NONE);
415 play_scene_button->set_tooltip_text(TTR("Run the currently edited scene."));
416 play_scene_button->connect("pressed", callable_mp(this, &EditorRunBar::_play_current_pressed));
417
418 ED_SHORTCUT_AND_COMMAND("editor/run_current_scene", TTR("Run Current Scene"), Key::F6);
419 ED_SHORTCUT_OVERRIDE("editor/run_current_scene", "macos", KeyModifierMask::META | Key::R);
420 play_scene_button->set_shortcut(ED_GET_SHORTCUT("editor/run_current_scene"));
421
422 play_custom_scene_button = memnew(Button);
423 main_hbox->add_child(play_custom_scene_button);
424 play_custom_scene_button->set_flat(true);
425 play_custom_scene_button->set_toggle_mode(true);
426 play_custom_scene_button->set_focus_mode(Control::FOCUS_NONE);
427 play_custom_scene_button->set_tooltip_text(TTR("Run a specific scene."));
428 play_custom_scene_button->connect("pressed", callable_mp(this, &EditorRunBar::_play_custom_pressed));
429
430 ED_SHORTCUT_AND_COMMAND("editor/run_specific_scene", TTR("Run Specific Scene"), KeyModifierMask::CTRL | KeyModifierMask::SHIFT | Key::F5);
431 ED_SHORTCUT_OVERRIDE("editor/run_specific_scene", "macos", KeyModifierMask::META | KeyModifierMask::SHIFT | Key::R);
432 play_custom_scene_button->set_shortcut(ED_GET_SHORTCUT("editor/run_specific_scene"));
433
434 write_movie_panel = memnew(PanelContainer);
435 main_hbox->add_child(write_movie_panel);
436
437 write_movie_button = memnew(Button);
438 write_movie_panel->add_child(write_movie_button);
439 write_movie_button->set_flat(true);
440 write_movie_button->set_toggle_mode(true);
441 write_movie_button->set_pressed(false);
442 write_movie_button->set_focus_mode(Control::FOCUS_NONE);
443 write_movie_button->set_tooltip_text(TTR("Enable Movie Maker mode.\nThe project will run at stable FPS and the visual and audio output will be recorded to a video file."));
444 write_movie_button->connect("toggled", callable_mp(this, &EditorRunBar::_write_movie_toggled));
445
446 quick_run = memnew(EditorQuickOpen);
447 add_child(quick_run);
448 quick_run->connect("quick_open", callable_mp(this, &EditorRunBar::_quick_run_selected));
449}
450