/**************************************************************************/ /* 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("panel", get_theme_stylebox(SNAME("LaunchPadMovieMode"), EditorStringName(EditorStyles))); write_movie_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("MovieWriterButtonPressed"), EditorStringName(EditorStyles))); } else { main_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("LaunchPadNormal"), EditorStringName(EditorStyles))); write_movie_panel->add_theme_style_override("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("panel", get_theme_stylebox(SNAME("LaunchPadMovieMode"), EditorStringName(EditorStyles))); write_movie_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("MovieWriterButtonPressed"), EditorStringName(EditorStyles))); } else { add_theme_style_override("panel", get_theme_stylebox(SNAME("LaunchPadNormal"), EditorStringName(EditorStyles))); write_movie_panel->add_theme_style_override("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 &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)); }