godot/editor/gui/editor_run_bar.cpp
2024-05-30 22:54:50 +02:00

453 lines
17 KiB
C++

/**************************************************************************/
/* editor_run_bar.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "editor_run_bar.h"
#include "core/config/project_settings.h"
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_command_palette.h"
#include "editor/editor_node.h"
#include "editor/editor_quick_open.h"
#include "editor/editor_run_native.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/panel_container.h"
EditorRunBar *EditorRunBar::singleton = nullptr;
void EditorRunBar::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_POSTINITIALIZE: {
_reset_play_buttons();
} break;
case NOTIFICATION_THEME_CHANGED: {
_update_play_buttons();
pause_button->set_icon(get_editor_theme_icon(SNAME("Pause")));
stop_button->set_icon(get_editor_theme_icon(SNAME("Stop")));
if (is_movie_maker_enabled()) {
main_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("LaunchPadMovieMode"), EditorStringName(EditorStyles)));
write_movie_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("MovieWriterButtonPressed"), EditorStringName(EditorStyles)));
} else {
main_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("LaunchPadNormal"), EditorStringName(EditorStyles)));
write_movie_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("MovieWriterButtonNormal"), EditorStringName(EditorStyles)));
}
write_movie_button->set_icon(get_editor_theme_icon(SNAME("MainMovieWrite")));
// This button behaves differently, so color it as such.
write_movie_button->begin_bulk_theme_override();
write_movie_button->add_theme_color_override("icon_normal_color", Color(1, 1, 1, 0.7));
write_movie_button->add_theme_color_override("icon_pressed_color", Color(0, 0, 0, 0.84));
write_movie_button->add_theme_color_override("icon_hover_color", Color(1, 1, 1, 0.9));
write_movie_button->end_bulk_theme_override();
} break;
}
}
void EditorRunBar::_reset_play_buttons() {
play_button->set_pressed(false);
play_button->set_icon(get_editor_theme_icon(SNAME("MainPlay")));
play_button->set_tooltip_text(TTR("Play the project."));
play_scene_button->set_pressed(false);
play_scene_button->set_icon(get_editor_theme_icon(SNAME("PlayScene")));
play_scene_button->set_tooltip_text(TTR("Play the edited scene."));
play_custom_scene_button->set_pressed(false);
play_custom_scene_button->set_icon(get_editor_theme_icon(SNAME("PlayCustom")));
play_custom_scene_button->set_tooltip_text(TTR("Play a custom scene."));
}
void EditorRunBar::_update_play_buttons() {
_reset_play_buttons();
if (!is_playing()) {
return;
}
Button *active_button = nullptr;
if (current_mode == RUN_CURRENT) {
active_button = play_scene_button;
} else if (current_mode == RUN_CUSTOM) {
active_button = play_custom_scene_button;
} else {
active_button = play_button;
}
if (active_button) {
active_button->set_pressed(true);
active_button->set_icon(get_editor_theme_icon(SNAME("Reload")));
active_button->set_tooltip_text(TTR("Reload the played scene."));
}
}
void EditorRunBar::_write_movie_toggled(bool p_enabled) {
if (p_enabled) {
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("LaunchPadMovieMode"), EditorStringName(EditorStyles)));
write_movie_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("MovieWriterButtonPressed"), EditorStringName(EditorStyles)));
} else {
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("LaunchPadNormal"), EditorStringName(EditorStyles)));
write_movie_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("MovieWriterButtonNormal"), EditorStringName(EditorStyles)));
}
}
void EditorRunBar::_quick_run_selected() {
play_custom_scene(quick_run->get_selected());
}
void EditorRunBar::_play_custom_pressed() {
if (editor_run.get_status() == EditorRun::STATUS_STOP || current_mode != RunMode::RUN_CUSTOM) {
stop_playing();
quick_run->popup_dialog("PackedScene", true);
quick_run->set_title(TTR("Quick Run Scene..."));
play_custom_scene_button->set_pressed(false);
} else {
// Reload if already running a custom scene.
String last_custom_scene = run_custom_filename; // This is necessary to have a copy of the string.
play_custom_scene(last_custom_scene);
}
}
void EditorRunBar::_play_current_pressed() {
if (editor_run.get_status() == EditorRun::STATUS_STOP || current_mode != RunMode::RUN_CURRENT) {
play_current_scene();
} else {
// Reload if already running the current scene.
play_current_scene(true);
}
}
void EditorRunBar::_run_scene(const String &p_scene_path) {
ERR_FAIL_COND_MSG(current_mode == RUN_CUSTOM && p_scene_path.is_empty(), "Attempting to run a custom scene with an empty path.");
if (editor_run.get_status() == EditorRun::STATUS_PLAY) {
return;
}
_reset_play_buttons();
String write_movie_file;
if (is_movie_maker_enabled()) {
if (current_mode == RUN_CURRENT) {
Node *scene_root = nullptr;
if (p_scene_path.is_empty()) {
scene_root = get_tree()->get_edited_scene_root();
} else {
int scene_index = EditorNode::get_editor_data().get_edited_scene_from_path(p_scene_path);
if (scene_index >= 0) {
scene_root = EditorNode::get_editor_data().get_edited_scene_root(scene_index);
}
}
if (scene_root && scene_root->has_meta("movie_file")) {
// If the scene file has a movie_file metadata set, use this as file.
// Quick workaround if you want to have multiple scenes that write to
// multiple movies.
write_movie_file = scene_root->get_meta("movie_file");
}
}
if (write_movie_file.is_empty()) {
write_movie_file = GLOBAL_GET("editor/movie_writer/movie_file");
}
if (write_movie_file.is_empty()) {
// TODO: Provide options to directly resolve the issue with a custom dialog.
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"));
return;
}
}
String run_filename;
switch (current_mode) {
case RUN_CUSTOM: {
run_filename = p_scene_path;
run_custom_filename = run_filename;
} break;
case RUN_CURRENT: {
if (!p_scene_path.is_empty()) {
run_filename = p_scene_path;
run_current_filename = run_filename;
break;
}
Node *scene_root = get_tree()->get_edited_scene_root();
if (!scene_root) {
EditorNode::get_singleton()->show_accept(TTR("There is no defined scene to run."), TTR("OK"));
return;
}
if (scene_root->get_scene_file_path().is_empty()) {
EditorNode::get_singleton()->save_before_run();
return;
}
run_filename = scene_root->get_scene_file_path();
run_current_filename = run_filename;
} break;
default: {
if (!EditorNode::get_singleton()->ensure_main_scene(false)) {
return;
}
run_filename = GLOBAL_GET("application/run/main_scene");
} break;
}
EditorNode::get_singleton()->try_autosave();
if (!EditorNode::get_singleton()->call_build()) {
return;
}
EditorDebuggerNode::get_singleton()->start();
Error error = editor_run.run(run_filename, write_movie_file);
if (error != OK) {
EditorDebuggerNode::get_singleton()->stop();
EditorNode::get_singleton()->show_accept(TTR("Could not start subprocess(es)!"), TTR("OK"));
return;
}
_update_play_buttons();
stop_button->set_disabled(false);
emit_signal(SNAME("play_pressed"));
}
void EditorRunBar::_run_native(const Ref<EditorExportPreset> &p_preset) {
EditorNode::get_singleton()->try_autosave();
if (run_native->is_deploy_debug_remote_enabled()) {
stop_playing();
if (!EditorNode::get_singleton()->call_build()) {
return; // Build failed.
}
EditorDebuggerNode::get_singleton()->start(p_preset->get_platform()->get_debug_protocol());
emit_signal(SNAME("play_pressed"));
editor_run.run_native_notify();
}
}
void EditorRunBar::play_main_scene(bool p_from_native) {
if (p_from_native) {
run_native->resume_run_native();
} else {
stop_playing();
current_mode = RunMode::RUN_MAIN;
_run_scene();
}
}
void EditorRunBar::play_current_scene(bool p_reload) {
String last_current_scene = run_current_filename; // This is necessary to have a copy of the string.
EditorNode::get_singleton()->save_default_environment();
stop_playing();
current_mode = RunMode::RUN_CURRENT;
if (p_reload) {
_run_scene(last_current_scene);
} else {
_run_scene();
}
}
void EditorRunBar::play_custom_scene(const String &p_custom) {
stop_playing();
current_mode = RunMode::RUN_CUSTOM;
_run_scene(p_custom);
}
void EditorRunBar::stop_playing() {
if (editor_run.get_status() == EditorRun::STATUS_STOP) {
return;
}
current_mode = RunMode::STOPPED;
editor_run.stop();
EditorDebuggerNode::get_singleton()->stop();
run_custom_filename.clear();
run_current_filename.clear();
stop_button->set_pressed(false);
stop_button->set_disabled(true);
_reset_play_buttons();
emit_signal(SNAME("stop_pressed"));
}
bool EditorRunBar::is_playing() const {
EditorRun::Status status = editor_run.get_status();
return (status == EditorRun::STATUS_PLAY || status == EditorRun::STATUS_PAUSED);
}
String EditorRunBar::get_playing_scene() const {
String run_filename = editor_run.get_running_scene();
if (run_filename.is_empty() && is_playing()) {
run_filename = GLOBAL_GET("application/run/main_scene"); // Must be the main scene then.
}
return run_filename;
}
Error EditorRunBar::start_native_device(int p_device_id) {
return run_native->start_run_native(p_device_id);
}
OS::ProcessID EditorRunBar::has_child_process(OS::ProcessID p_pid) const {
return editor_run.has_child_process(p_pid);
}
void EditorRunBar::stop_child_process(OS::ProcessID p_pid) {
if (!has_child_process(p_pid)) {
return;
}
editor_run.stop_child_process(p_pid);
if (!editor_run.get_child_process_count()) { // All children stopped. Closing.
stop_playing();
}
}
void EditorRunBar::set_movie_maker_enabled(bool p_enabled) {
write_movie_button->set_pressed(p_enabled);
}
bool EditorRunBar::is_movie_maker_enabled() const {
return write_movie_button->is_pressed();
}
HBoxContainer *EditorRunBar::get_buttons_container() {
return main_hbox;
}
void EditorRunBar::_bind_methods() {
ADD_SIGNAL(MethodInfo("play_pressed"));
ADD_SIGNAL(MethodInfo("stop_pressed"));
}
EditorRunBar::EditorRunBar() {
singleton = this;
main_panel = memnew(PanelContainer);
add_child(main_panel);
main_hbox = memnew(HBoxContainer);
main_panel->add_child(main_hbox);
play_button = memnew(Button);
main_hbox->add_child(play_button);
play_button->set_theme_type_variation("RunBarButton");
play_button->set_toggle_mode(true);
play_button->set_focus_mode(Control::FOCUS_NONE);
play_button->set_tooltip_text(TTR("Run the project's default scene."));
play_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::play_main_scene).bind(false));
ED_SHORTCUT_AND_COMMAND("editor/run_project", TTR("Run Project"), Key::F5);
ED_SHORTCUT_OVERRIDE("editor/run_project", "macos", KeyModifierMask::META | Key::B);
play_button->set_shortcut(ED_GET_SHORTCUT("editor/run_project"));
pause_button = memnew(Button);
main_hbox->add_child(pause_button);
pause_button->set_theme_type_variation("RunBarButton");
pause_button->set_toggle_mode(true);
pause_button->set_focus_mode(Control::FOCUS_NONE);
pause_button->set_tooltip_text(TTR("Pause the running project's execution for debugging."));
pause_button->set_disabled(true);
ED_SHORTCUT("editor/pause_running_project", TTR("Pause Running Project"), Key::F7);
ED_SHORTCUT_OVERRIDE("editor/pause_running_project", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::Y);
pause_button->set_shortcut(ED_GET_SHORTCUT("editor/pause_running_project"));
stop_button = memnew(Button);
main_hbox->add_child(stop_button);
stop_button->set_theme_type_variation("RunBarButton");
stop_button->set_focus_mode(Control::FOCUS_NONE);
stop_button->set_tooltip_text(TTR("Stop the currently running project."));
stop_button->set_disabled(true);
stop_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::stop_playing));
ED_SHORTCUT("editor/stop_running_project", TTR("Stop Running Project"), Key::F8);
ED_SHORTCUT_OVERRIDE("editor/stop_running_project", "macos", KeyModifierMask::META | Key::PERIOD);
stop_button->set_shortcut(ED_GET_SHORTCUT("editor/stop_running_project"));
run_native = memnew(EditorRunNative);
main_hbox->add_child(run_native);
run_native->connect("native_run", callable_mp(this, &EditorRunBar::_run_native));
play_scene_button = memnew(Button);
main_hbox->add_child(play_scene_button);
play_scene_button->set_theme_type_variation("RunBarButton");
play_scene_button->set_toggle_mode(true);
play_scene_button->set_focus_mode(Control::FOCUS_NONE);
play_scene_button->set_tooltip_text(TTR("Run the currently edited scene."));
play_scene_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::_play_current_pressed));
ED_SHORTCUT_AND_COMMAND("editor/run_current_scene", TTR("Run Current Scene"), Key::F6);
ED_SHORTCUT_OVERRIDE("editor/run_current_scene", "macos", KeyModifierMask::META | Key::R);
play_scene_button->set_shortcut(ED_GET_SHORTCUT("editor/run_current_scene"));
play_custom_scene_button = memnew(Button);
main_hbox->add_child(play_custom_scene_button);
play_custom_scene_button->set_theme_type_variation("RunBarButton");
play_custom_scene_button->set_toggle_mode(true);
play_custom_scene_button->set_focus_mode(Control::FOCUS_NONE);
play_custom_scene_button->set_tooltip_text(TTR("Run a specific scene."));
play_custom_scene_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::_play_custom_pressed));
ED_SHORTCUT_AND_COMMAND("editor/run_specific_scene", TTR("Run Specific Scene"), KeyModifierMask::CTRL | KeyModifierMask::SHIFT | Key::F5);
ED_SHORTCUT_OVERRIDE("editor/run_specific_scene", "macos", KeyModifierMask::META | KeyModifierMask::SHIFT | Key::R);
play_custom_scene_button->set_shortcut(ED_GET_SHORTCUT("editor/run_specific_scene"));
write_movie_panel = memnew(PanelContainer);
main_hbox->add_child(write_movie_panel);
write_movie_button = memnew(Button);
write_movie_panel->add_child(write_movie_button);
write_movie_button->set_theme_type_variation("RunBarButton");
write_movie_button->set_toggle_mode(true);
write_movie_button->set_pressed(false);
write_movie_button->set_focus_mode(Control::FOCUS_NONE);
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."));
write_movie_button->connect("toggled", callable_mp(this, &EditorRunBar::_write_movie_toggled));
quick_run = memnew(EditorQuickOpen);
add_child(quick_run);
quick_run->connect("quick_open", callable_mp(this, &EditorRunBar::_quick_run_selected));
}