initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
This commit is contained in:
6
editor/run/SCsub
Normal file
6
editor/run/SCsub
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
env.add_source_files(env.editor_sources, "*.cpp")
|
332
editor/run/editor_run.cpp
Normal file
332
editor/run/editor_run.cpp
Normal file
@@ -0,0 +1,332 @@
|
||||
/**************************************************************************/
|
||||
/* editor_run.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.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "editor/debugger/editor_debugger_node.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/run/run_instances_dialog.h"
|
||||
#include "editor/settings/editor_settings.h"
|
||||
#include "main/main.h"
|
||||
#include "servers/display_server.h"
|
||||
|
||||
EditorRun::Status EditorRun::get_status() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
String EditorRun::get_running_scene() const {
|
||||
return running_scene;
|
||||
}
|
||||
|
||||
Error EditorRun::run(const String &p_scene, const String &p_write_movie, const Vector<String> &p_run_args) {
|
||||
List<String> args;
|
||||
|
||||
for (const String &a : Main::get_forwardable_cli_arguments(Main::CLI_SCOPE_PROJECT)) {
|
||||
args.push_back(a);
|
||||
}
|
||||
|
||||
String resource_path = ProjectSettings::get_singleton()->get_resource_path();
|
||||
if (!resource_path.is_empty()) {
|
||||
args.push_back("--path");
|
||||
args.push_back(resource_path.replace(" ", "%20"));
|
||||
}
|
||||
|
||||
const String debug_uri = EditorDebuggerNode::get_singleton()->get_server_uri();
|
||||
if (debug_uri.size()) {
|
||||
args.push_back("--remote-debug");
|
||||
args.push_back(debug_uri);
|
||||
}
|
||||
|
||||
args.push_back("--editor-pid");
|
||||
args.push_back(itos(OS::get_singleton()->get_process_id()));
|
||||
|
||||
bool debug_collisions = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_collisions", false);
|
||||
bool debug_paths = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_paths", false);
|
||||
bool debug_navigation = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_navigation", false);
|
||||
bool debug_avoidance = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_avoidance", false);
|
||||
bool debug_canvas_redraw = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_canvas_redraw", false);
|
||||
bool debug_mute_audio = EditorDebuggerNode::get_singleton()->get_debug_mute_audio();
|
||||
|
||||
if (debug_collisions) {
|
||||
args.push_back("--debug-collisions");
|
||||
}
|
||||
|
||||
if (debug_paths) {
|
||||
args.push_back("--debug-paths");
|
||||
}
|
||||
|
||||
if (debug_navigation) {
|
||||
args.push_back("--debug-navigation");
|
||||
}
|
||||
|
||||
if (debug_avoidance) {
|
||||
args.push_back("--debug-avoidance");
|
||||
}
|
||||
|
||||
if (debug_canvas_redraw) {
|
||||
args.push_back("--debug-canvas-item-redraw");
|
||||
}
|
||||
|
||||
if (debug_mute_audio) {
|
||||
args.push_back("--debug-mute-audio");
|
||||
}
|
||||
|
||||
if (p_write_movie != "") {
|
||||
args.push_back("--write-movie");
|
||||
args.push_back(p_write_movie);
|
||||
args.push_back("--fixed-fps");
|
||||
args.push_back(itos(GLOBAL_GET("editor/movie_writer/fps")));
|
||||
if (bool(GLOBAL_GET("editor/movie_writer/disable_vsync"))) {
|
||||
args.push_back("--disable-vsync");
|
||||
}
|
||||
}
|
||||
|
||||
WindowPlacement window_placement = get_window_placement();
|
||||
if (window_placement.position != Point2i(INT_MAX, INT_MAX)) {
|
||||
args.push_back("--position");
|
||||
args.push_back(itos(window_placement.position.x) + "," + itos(window_placement.position.y));
|
||||
}
|
||||
|
||||
if (window_placement.force_maximized) {
|
||||
args.push_back("--maximized");
|
||||
} else if (window_placement.force_fullscreen) {
|
||||
args.push_back("--fullscreen");
|
||||
}
|
||||
|
||||
List<String> breakpoints;
|
||||
EditorNode::get_editor_data().get_editor_breakpoints(&breakpoints);
|
||||
|
||||
if (!breakpoints.is_empty()) {
|
||||
args.push_back("--breakpoints");
|
||||
String bpoints;
|
||||
for (const List<String>::Element *E = breakpoints.front(); E; E = E->next()) {
|
||||
bpoints += E->get().replace(" ", "%20");
|
||||
if (E->next()) {
|
||||
bpoints += ",";
|
||||
}
|
||||
}
|
||||
|
||||
args.push_back(bpoints);
|
||||
}
|
||||
|
||||
if (EditorDebuggerNode::get_singleton()->is_skip_breakpoints()) {
|
||||
args.push_back("--skip-breakpoints");
|
||||
}
|
||||
|
||||
if (EditorDebuggerNode::get_singleton()->is_ignore_error_breaks()) {
|
||||
args.push_back("--ignore-error-breaks");
|
||||
}
|
||||
|
||||
if (!p_scene.is_empty()) {
|
||||
args.push_back("--scene");
|
||||
args.push_back(p_scene);
|
||||
}
|
||||
|
||||
if (!p_run_args.is_empty()) {
|
||||
for (const String &run_arg : p_run_args) {
|
||||
args.push_back(run_arg);
|
||||
}
|
||||
}
|
||||
|
||||
String exec = OS::get_singleton()->get_executable_path();
|
||||
int instance_count = RunInstancesDialog::get_singleton()->get_instance_count();
|
||||
for (int i = 0; i < instance_count; i++) {
|
||||
List<String> instance_args(args);
|
||||
RunInstancesDialog::get_singleton()->get_argument_list_for_instance(i, instance_args);
|
||||
RunInstancesDialog::get_singleton()->apply_custom_features(i);
|
||||
if (instance_starting_callback) {
|
||||
instance_starting_callback(i, instance_args);
|
||||
}
|
||||
|
||||
if (OS::get_singleton()->is_stdout_verbose()) {
|
||||
print_line(vformat("Running: %s", exec));
|
||||
for (const String &E : instance_args) {
|
||||
print_line(" %s", E);
|
||||
}
|
||||
}
|
||||
|
||||
OS::ProcessID pid = 0;
|
||||
Error err = OS::get_singleton()->create_instance(instance_args, &pid);
|
||||
ERR_FAIL_COND_V(err, err);
|
||||
if (pid != 0) {
|
||||
pids.push_back(pid);
|
||||
}
|
||||
}
|
||||
|
||||
status = STATUS_PLAY;
|
||||
if (!p_scene.is_empty()) {
|
||||
running_scene = p_scene;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
bool EditorRun::request_screenshot(const Callable &p_callback) {
|
||||
if (instance_rq_screenshot_callback) {
|
||||
return instance_rq_screenshot_callback(p_callback);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorRun::has_child_process(OS::ProcessID p_pid) const {
|
||||
for (const OS::ProcessID &E : pids) {
|
||||
if (E == p_pid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void EditorRun::stop_child_process(OS::ProcessID p_pid) {
|
||||
if (has_child_process(p_pid)) {
|
||||
OS::get_singleton()->kill(p_pid);
|
||||
pids.erase(p_pid);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRun::stop() {
|
||||
if (status != STATUS_STOP && pids.size() > 0) {
|
||||
for (const OS::ProcessID &E : pids) {
|
||||
OS::get_singleton()->kill(E);
|
||||
}
|
||||
pids.clear();
|
||||
}
|
||||
|
||||
status = STATUS_STOP;
|
||||
running_scene = "";
|
||||
}
|
||||
|
||||
OS::ProcessID EditorRun::get_current_process() const {
|
||||
if (pids.front() == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return pids.front()->get();
|
||||
}
|
||||
|
||||
EditorRun::WindowPlacement EditorRun::get_window_placement() {
|
||||
WindowPlacement placement = WindowPlacement();
|
||||
placement.screen = EDITOR_GET("run/window_placement/screen");
|
||||
if (placement.screen == -5) {
|
||||
// Same as editor
|
||||
placement.screen = DisplayServer::get_singleton()->window_get_current_screen();
|
||||
} else if (placement.screen == -4) {
|
||||
// Previous monitor (wrap to the other end if needed)
|
||||
placement.screen = Math::wrapi(
|
||||
DisplayServer::get_singleton()->window_get_current_screen() - 1,
|
||||
0,
|
||||
DisplayServer::get_singleton()->get_screen_count());
|
||||
} else if (placement.screen == -3) {
|
||||
// Next monitor (wrap to the other end if needed)
|
||||
placement.screen = Math::wrapi(
|
||||
DisplayServer::get_singleton()->window_get_current_screen() + 1,
|
||||
0,
|
||||
DisplayServer::get_singleton()->get_screen_count());
|
||||
} else if (placement.screen == -2) {
|
||||
// Primary screen
|
||||
placement.screen = DisplayServer::get_singleton()->get_primary_screen();
|
||||
}
|
||||
|
||||
placement.size.x = GLOBAL_GET("display/window/size/viewport_width");
|
||||
placement.size.y = GLOBAL_GET("display/window/size/viewport_height");
|
||||
|
||||
Size2 desired_size;
|
||||
desired_size.x = GLOBAL_GET("display/window/size/window_width_override");
|
||||
desired_size.y = GLOBAL_GET("display/window/size/window_height_override");
|
||||
if (desired_size.x > 0 && desired_size.y > 0) {
|
||||
placement.size = desired_size;
|
||||
}
|
||||
|
||||
Rect2 screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(placement.screen);
|
||||
|
||||
int window_placement = EDITOR_GET("run/window_placement/rect");
|
||||
if (screen_rect != Rect2()) {
|
||||
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_HIDPI)) {
|
||||
bool hidpi_proj = GLOBAL_GET("display/window/dpi/allow_hidpi");
|
||||
int display_scale = 1;
|
||||
|
||||
if (OS::get_singleton()->is_hidpi_allowed()) {
|
||||
if (hidpi_proj) {
|
||||
display_scale = 1; // Both editor and project runs in hiDPI mode, do not scale.
|
||||
} else {
|
||||
display_scale = DisplayServer::get_singleton()->screen_get_max_scale(); // Editor is in hiDPI mode, project is not, scale down.
|
||||
}
|
||||
} else {
|
||||
if (hidpi_proj) {
|
||||
display_scale = (1.f / DisplayServer::get_singleton()->screen_get_max_scale()); // Editor is not in hiDPI mode, project is, scale up.
|
||||
} else {
|
||||
display_scale = 1; // Both editor and project runs in lowDPI mode, do not scale.
|
||||
}
|
||||
}
|
||||
screen_rect.position /= display_scale;
|
||||
screen_rect.size /= display_scale;
|
||||
}
|
||||
|
||||
switch (window_placement) {
|
||||
case 0: { // top left
|
||||
placement.position = screen_rect.position;
|
||||
} break;
|
||||
case 1: { // centered
|
||||
placement.position = (screen_rect.position) + ((screen_rect.size - placement.size) / 2).floor();
|
||||
} break;
|
||||
case 2: { // custom pos
|
||||
Vector2 pos = EDITOR_GET("run/window_placement/rect_custom_position");
|
||||
pos += screen_rect.position;
|
||||
placement.position = pos;
|
||||
} break;
|
||||
case 3: { // force maximized
|
||||
placement.force_maximized = true;
|
||||
placement.position = (screen_rect.position) + ((screen_rect.size - placement.size) / 2).floor();
|
||||
} break;
|
||||
case 4: { // force fullscreen
|
||||
placement.force_fullscreen = true;
|
||||
placement.position = (screen_rect.position) + ((screen_rect.size - placement.size) / 2).floor();
|
||||
} break;
|
||||
}
|
||||
} else {
|
||||
// Unable to get screen info, skip setting position.
|
||||
switch (window_placement) {
|
||||
case 3: { // force maximized
|
||||
placement.force_maximized = true;
|
||||
} break;
|
||||
case 4: { // force fullscreen
|
||||
placement.force_fullscreen = true;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
return placement;
|
||||
}
|
||||
|
||||
EditorRun::EditorRun() {
|
||||
status = STATUS_STOP;
|
||||
running_scene = "";
|
||||
}
|
81
editor/run/editor_run.h
Normal file
81
editor/run/editor_run.h
Normal file
@@ -0,0 +1,81 @@
|
||||
/**************************************************************************/
|
||||
/* editor_run.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/os/os.h"
|
||||
|
||||
typedef void (*EditorRunInstanceStarting)(int p_index, List<String> &r_arguments);
|
||||
typedef bool (*EditorRunInstanceRequestScreenshot)(const Callable &p_callback);
|
||||
|
||||
class EditorRun {
|
||||
public:
|
||||
enum Status {
|
||||
STATUS_PLAY,
|
||||
STATUS_PAUSED,
|
||||
STATUS_STOP
|
||||
};
|
||||
|
||||
List<OS::ProcessID> pids;
|
||||
|
||||
struct WindowPlacement {
|
||||
int screen = 0;
|
||||
Point2i position = Point2i(INT_MAX, INT_MAX);
|
||||
Size2i size;
|
||||
bool force_maximized = false;
|
||||
bool force_fullscreen = false;
|
||||
};
|
||||
|
||||
private:
|
||||
Status status;
|
||||
String running_scene;
|
||||
|
||||
public:
|
||||
inline static EditorRunInstanceStarting instance_starting_callback = nullptr;
|
||||
inline static EditorRunInstanceRequestScreenshot instance_rq_screenshot_callback = nullptr;
|
||||
|
||||
Status get_status() const;
|
||||
String get_running_scene() const;
|
||||
|
||||
Error run(const String &p_scene, const String &p_write_movie = "", const Vector<String> &p_run_args = Vector<String>());
|
||||
void run_native_notify() { status = STATUS_PLAY; }
|
||||
void stop();
|
||||
|
||||
void stop_child_process(OS::ProcessID p_pid);
|
||||
bool has_child_process(OS::ProcessID p_pid) const;
|
||||
int get_child_process_count() const { return pids.size(); }
|
||||
OS::ProcessID get_current_process() const;
|
||||
|
||||
static bool request_screenshot(const Callable &p_callback);
|
||||
|
||||
static WindowPlacement get_window_placement();
|
||||
|
||||
EditorRun();
|
||||
};
|
678
editor/run/editor_run_bar.cpp
Normal file
678
editor/run/editor_run_bar.cpp
Normal file
@@ -0,0 +1,678 @@
|
||||
/**************************************************************************/
|
||||
/* 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/debugger/script_editor_debugger.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/gui/editor_bottom_panel.h"
|
||||
#include "editor/gui/editor_quick_open_dialog.h"
|
||||
#include "editor/gui/editor_toaster.h"
|
||||
#include "editor/run/editor_run_native.h"
|
||||
#include "editor/settings/editor_command_palette.h"
|
||||
#include "editor/settings/editor_settings.h"
|
||||
#include "editor/settings/project_settings_editor.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/menu_button.h"
|
||||
#include "scene/gui/panel_container.h"
|
||||
|
||||
#ifndef XR_DISABLED
|
||||
#include "servers/xr_server.h"
|
||||
#endif // XR_DISABLED
|
||||
|
||||
EditorRunBar *EditorRunBar::singleton = nullptr;
|
||||
|
||||
void EditorRunBar::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_POSTINITIALIZE: {
|
||||
_reset_play_buttons();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_READY: {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
recovery_mode_show_dialog();
|
||||
}
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
main_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("LaunchPadRecoveryMode"), EditorStringName(EditorStyles)));
|
||||
recovery_mode_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("RecoveryModeButton"), EditorStringName(EditorStyles)));
|
||||
recovery_mode_button->add_theme_style_override("hover", get_theme_stylebox(SNAME("RecoveryModeButton"), EditorStringName(EditorStyles)));
|
||||
|
||||
recovery_mode_button->set_button_icon(get_editor_theme_icon(SNAME("NodeWarning")));
|
||||
recovery_mode_reload_button->set_button_icon(get_editor_theme_icon(SNAME("Reload")));
|
||||
|
||||
recovery_mode_button->begin_bulk_theme_override();
|
||||
recovery_mode_button->add_theme_color_override("icon_normal_color", Color(0.3, 0.3, 0.3, 1));
|
||||
recovery_mode_button->add_theme_color_override("icon_pressed_color", Color(0.4, 0.4, 0.4, 1));
|
||||
recovery_mode_button->add_theme_color_override("icon_hover_color", Color(0.6, 0.6, 0.6, 1));
|
||||
Color dark_color = get_theme_color("recovery_mode_text_color", EditorStringName(Editor));
|
||||
recovery_mode_button->add_theme_color_override(SceneStringName(font_color), dark_color);
|
||||
recovery_mode_button->add_theme_color_override("font_pressed_color", dark_color.lightened(0.2));
|
||||
recovery_mode_button->add_theme_color_override("font_hover_color", dark_color.lightened(0.4));
|
||||
recovery_mode_button->add_theme_color_override("font_hover_pressed_color", dark_color.lightened(0.2));
|
||||
recovery_mode_button->end_bulk_theme_override();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_update_play_buttons();
|
||||
profiler_autostart_indicator->set_button_icon(get_editor_theme_icon(SNAME("ProfilerAutostartWarning")));
|
||||
pause_button->set_button_icon(get_editor_theme_icon(SNAME("Pause")));
|
||||
stop_button->set_button_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_button->set_theme_type_variation("RunBarButtonMovieMakerEnabled");
|
||||
|
||||
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_button->set_theme_type_variation("RunBarButtonMovieMakerDisabled");
|
||||
|
||||
write_movie_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("MovieWriterButtonNormal"), EditorStringName(EditorStyles)));
|
||||
}
|
||||
|
||||
write_movie_button->set_button_icon(get_editor_theme_icon(SNAME("MainMovieWrite")));
|
||||
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRunBar::_reset_play_buttons() {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
return;
|
||||
}
|
||||
|
||||
play_button->set_pressed(false);
|
||||
play_button->set_button_icon(get_editor_theme_icon(SNAME("MainPlay")));
|
||||
play_button->set_tooltip_text(TTRC("Play the project."));
|
||||
|
||||
play_scene_button->set_pressed(false);
|
||||
play_scene_button->set_button_icon(get_editor_theme_icon(SNAME("PlayScene")));
|
||||
play_scene_button->set_tooltip_text(TTRC("Play the edited scene."));
|
||||
|
||||
play_custom_scene_button->set_pressed(false);
|
||||
play_custom_scene_button->set_button_icon(get_editor_theme_icon(SNAME("PlayCustom")));
|
||||
play_custom_scene_button->set_tooltip_text(TTRC("Play a custom scene."));
|
||||
}
|
||||
|
||||
void EditorRunBar::_update_play_buttons() {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_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_button_icon(get_editor_theme_icon(SNAME("Reload")));
|
||||
active_button->set_tooltip_text(TTRC("Reload the played scene."));
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRunBar::_movie_maker_item_pressed(int p_id) {
|
||||
switch (p_id) {
|
||||
case MOVIE_MAKER_TOGGLE: {
|
||||
bool new_enabled = !is_movie_maker_enabled();
|
||||
set_movie_maker_enabled(new_enabled);
|
||||
write_movie_button->get_popup()->set_item_checked(0, new_enabled);
|
||||
write_movie_button->set_pressed(new_enabled);
|
||||
_write_movie_toggled(new_enabled);
|
||||
break;
|
||||
}
|
||||
case MOVIE_MAKER_OPEN_SETTINGS:
|
||||
ProjectSettingsEditor::get_singleton()->popup_project_settings(true);
|
||||
ProjectSettingsEditor::get_singleton()->set_general_page("editor/movie_writer");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
Vector<String> EditorRunBar::_get_xr_mode_play_args(int p_xr_mode_id) {
|
||||
Vector<String> play_args;
|
||||
if (p_xr_mode_id == 0) {
|
||||
// Play in regular mode, xr mode off.
|
||||
play_args.push_back("--xr-mode");
|
||||
play_args.push_back("off");
|
||||
} else if (p_xr_mode_id == 1) {
|
||||
// Play in xr mode.
|
||||
play_args.push_back("--xr-mode");
|
||||
play_args.push_back("on");
|
||||
}
|
||||
return play_args;
|
||||
}
|
||||
|
||||
void EditorRunBar::_quick_run_selected(const String &p_file_path, int p_id) {
|
||||
play_custom_scene(p_file_path, _get_xr_mode_play_args(p_id));
|
||||
}
|
||||
|
||||
void EditorRunBar::_play_custom_pressed(int p_id) {
|
||||
if (editor_run.get_status() == EditorRun::STATUS_STOP || current_mode != RunMode::RUN_CUSTOM) {
|
||||
stop_playing();
|
||||
|
||||
EditorNode::get_singleton()->get_quick_open_dialog()->popup_dialog({ "PackedScene" }, callable_mp(this, &EditorRunBar::_quick_run_selected).bind(p_id));
|
||||
play_custom_scene_button->set_pressed(false);
|
||||
} else {
|
||||
Vector<String> play_args = _get_xr_mode_play_args(p_id);
|
||||
|
||||
// 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, play_args);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRunBar::_play_current_pressed(int p_id) {
|
||||
Vector<String> play_args = _get_xr_mode_play_args(p_id);
|
||||
|
||||
if (editor_run.get_status() == EditorRun::STATUS_STOP || current_mode != RunMode::RUN_CURRENT) {
|
||||
play_current_scene(false, play_args);
|
||||
} else {
|
||||
// Reload if already running the current scene.
|
||||
play_current_scene(true, play_args);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRunBar::_run_scene(const String &p_scene_path, const Vector<String> &p_run_args) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (!EditorNode::get_singleton()->validate_custom_directory()) {
|
||||
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 = ResourceUID::ensure_path(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, p_run_args);
|
||||
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::_profiler_autostart_indicator_pressed() {
|
||||
// Switch to the first profiler tab in the bottom panel.
|
||||
EditorNode::get_singleton()->get_bottom_panel()->make_item_visible(EditorDebuggerNode::get_singleton(), true);
|
||||
|
||||
if (EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_profiler", false)) {
|
||||
EditorDebuggerNode::get_singleton()->get_current_debugger()->switch_to_debugger(3);
|
||||
} else if (EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_visual_profiler", false)) {
|
||||
EditorDebuggerNode::get_singleton()->get_current_debugger()->switch_to_debugger(4);
|
||||
} else {
|
||||
// Switch to the network profiler tab.
|
||||
EditorDebuggerNode::get_singleton()->get_current_debugger()->switch_to_debugger(8);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRunBar::recovery_mode_show_dialog() {
|
||||
recovery_mode_popup->popup_centered();
|
||||
}
|
||||
|
||||
void EditorRunBar::recovery_mode_reload_project() {
|
||||
EditorNode::get_singleton()->trigger_menu_option(EditorNode::PROJECT_RELOAD_CURRENT_PROJECT, false);
|
||||
}
|
||||
|
||||
void EditorRunBar::play_main_scene(bool p_from_native) {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
EditorToaster::get_singleton()->popup_str(TTR("Recovery Mode is enabled. Disable it to run the project."), EditorToaster::SEVERITY_WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
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, const Vector<String> &p_play_args) {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
EditorToaster::get_singleton()->popup_str(TTR("Recovery Mode is enabled. Disable it to run the project."), EditorToaster::SEVERITY_WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
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, p_play_args);
|
||||
} else {
|
||||
_run_scene("", p_play_args);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRunBar::play_custom_scene(const String &p_custom, const Vector<String> &p_play_args) {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
EditorToaster::get_singleton()->popup_str(TTR("Recovery Mode is enabled. Disable it to run the project."), EditorToaster::SEVERITY_WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
stop_playing();
|
||||
|
||||
current_mode = RunMode::RUN_CUSTOM;
|
||||
_run_scene(p_custom, p_play_args);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
OS::ProcessID EditorRunBar::get_current_process() const {
|
||||
return editor_run.get_current_process();
|
||||
}
|
||||
|
||||
void EditorRunBar::set_movie_maker_enabled(bool p_enabled) {
|
||||
movie_maker_enabled = p_enabled;
|
||||
write_movie_button->get_popup()->set_item_checked(0, p_enabled);
|
||||
}
|
||||
|
||||
bool EditorRunBar::is_movie_maker_enabled() const {
|
||||
return movie_maker_enabled;
|
||||
}
|
||||
|
||||
void EditorRunBar::update_profiler_autostart_indicator() {
|
||||
bool profiler_active = EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_profiler", false);
|
||||
bool visual_profiler_active = EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_visual_profiler", false);
|
||||
bool network_profiler_active = EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_network_profiler", false);
|
||||
bool any_profiler_active = profiler_active | visual_profiler_active | network_profiler_active;
|
||||
any_profiler_active &= !Engine::get_singleton()->is_recovery_mode_hint();
|
||||
profiler_autostart_indicator->set_visible(any_profiler_active);
|
||||
if (any_profiler_active) {
|
||||
String tooltip = TTR("Autostart is enabled for the following profilers, which can have a performance impact:");
|
||||
if (profiler_active) {
|
||||
tooltip += "\n- " + TTR("Profiler");
|
||||
}
|
||||
if (visual_profiler_active) {
|
||||
tooltip += "\n- " + TTR("Visual Profiler");
|
||||
}
|
||||
if (network_profiler_active) {
|
||||
tooltip += "\n- " + TTR("Network Profiler");
|
||||
}
|
||||
tooltip += "\n\n" + TTR("Click to open the first profiler for which autostart is enabled.");
|
||||
profiler_autostart_indicator->set_tooltip_text(tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
outer_hbox = memnew(HBoxContainer);
|
||||
add_child(outer_hbox);
|
||||
|
||||
// Use a button for the indicator since it comes with a background panel and pixel perfect centering of an icon.
|
||||
profiler_autostart_indicator = memnew(Button);
|
||||
profiler_autostart_indicator->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
profiler_autostart_indicator->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER);
|
||||
profiler_autostart_indicator->set_focus_mode(FOCUS_ACCESSIBILITY);
|
||||
profiler_autostart_indicator->set_theme_type_variation("ProfilerAutostartIndicator");
|
||||
profiler_autostart_indicator->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::_profiler_autostart_indicator_pressed));
|
||||
outer_hbox->add_child(profiler_autostart_indicator);
|
||||
update_profiler_autostart_indicator();
|
||||
|
||||
main_panel = memnew(PanelContainer);
|
||||
outer_hbox->add_child(main_panel);
|
||||
|
||||
main_hbox = memnew(HBoxContainer);
|
||||
main_panel->add_child(main_hbox);
|
||||
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
recovery_mode_popup = memnew(AcceptDialog);
|
||||
recovery_mode_popup->set_min_size(Size2(550, 70) * EDSCALE);
|
||||
recovery_mode_popup->set_title(TTR("Recovery Mode"));
|
||||
recovery_mode_popup->set_text(
|
||||
TTR("Godot opened the project in Recovery Mode, which is a special mode that can help recover projects that crash the engine upon initialization. The following features have been temporarily disabled:") +
|
||||
String::utf8("\n\n• ") + TTR("Tool scripts") +
|
||||
String::utf8("\n• ") + TTR("Editor plugins") +
|
||||
String::utf8("\n• ") + TTR("GDExtension addons") +
|
||||
String::utf8("\n• ") + TTR("Automatic scene restoring") +
|
||||
String::utf8("\n\n") + TTR("If the project cannot be opened outside of this mode, then it's very likely any of these components is preventing this project from launching. This mode is intended only for basic editing to troubleshoot such issues, and therefore it is not possible to run a project in this mode.") +
|
||||
String::utf8("\n\n") + TTR("To disable Recovery Mode, reload the project by pressing the Reload button next to the Recovery Mode banner, or by reopening the project normally."));
|
||||
recovery_mode_popup->set_autowrap(true);
|
||||
add_child(recovery_mode_popup);
|
||||
|
||||
recovery_mode_reload_button = memnew(Button);
|
||||
main_hbox->add_child(recovery_mode_reload_button);
|
||||
recovery_mode_reload_button->set_theme_type_variation("RunBarButton");
|
||||
recovery_mode_reload_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
|
||||
recovery_mode_reload_button->set_tooltip_text(TTR("Disable recovery mode and reload the project."));
|
||||
recovery_mode_reload_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::recovery_mode_reload_project));
|
||||
|
||||
recovery_mode_panel = memnew(PanelContainer);
|
||||
main_hbox->add_child(recovery_mode_panel);
|
||||
|
||||
recovery_mode_button = memnew(Button);
|
||||
recovery_mode_panel->add_child(recovery_mode_button);
|
||||
recovery_mode_button->set_theme_type_variation("RunBarButton");
|
||||
recovery_mode_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
|
||||
recovery_mode_button->set_text(TTR("Recovery Mode"));
|
||||
recovery_mode_button->set_tooltip_text(TTR("Recovery Mode is enabled. Click for more details."));
|
||||
recovery_mode_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::recovery_mode_show_dialog));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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_ACCESSIBILITY);
|
||||
play_button->set_tooltip_text(TTRC("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", TTRC("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_ACCESSIBILITY);
|
||||
pause_button->set_tooltip_text(TTRC("Pause the running project's execution for debugging."));
|
||||
pause_button->set_disabled(true);
|
||||
|
||||
ED_SHORTCUT("editor/pause_running_project", TTRC("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_ACCESSIBILITY);
|
||||
stop_button->set_tooltip_text(TTRC("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", TTRC("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));
|
||||
|
||||
bool add_play_xr_mode_options = false;
|
||||
#ifndef XR_DISABLED
|
||||
if (XRServer::get_xr_mode() == XRServer::XRMODE_ON ||
|
||||
(XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT && GLOBAL_GET("xr/openxr/enabled"))) {
|
||||
// If OpenXR is enabled, we turn the `play_scene_button` and
|
||||
// `play_custom_scene_button` into MenuButtons to provide the option to start a scene in
|
||||
// either regular mode or XR mode.
|
||||
add_play_xr_mode_options = true;
|
||||
}
|
||||
#endif // XR_DISABLED
|
||||
|
||||
if (add_play_xr_mode_options) {
|
||||
MenuButton *menu_button = memnew(MenuButton);
|
||||
PopupMenu *popup = menu_button->get_popup();
|
||||
popup->add_item(TTRC("Run Scene in Regular Mode"), 0);
|
||||
popup->add_item(TTRC("Run Scene in XR Mode"), 1);
|
||||
popup->connect(SceneStringName(id_pressed), callable_mp(this, &EditorRunBar::_play_current_pressed));
|
||||
play_scene_button = menu_button;
|
||||
} else {
|
||||
play_scene_button = memnew(Button);
|
||||
play_scene_button->set_toggle_mode(true);
|
||||
play_scene_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::_play_current_pressed).bind(-1));
|
||||
}
|
||||
main_hbox->add_child(play_scene_button);
|
||||
play_scene_button->set_theme_type_variation("RunBarButton");
|
||||
play_scene_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
|
||||
play_scene_button->set_tooltip_text(TTRC("Run the currently edited scene."));
|
||||
|
||||
ED_SHORTCUT_AND_COMMAND("editor/run_current_scene", TTRC("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"));
|
||||
|
||||
if (add_play_xr_mode_options) {
|
||||
MenuButton *menu_button = memnew(MenuButton);
|
||||
PopupMenu *popup = menu_button->get_popup();
|
||||
popup->add_item(TTRC("Run in Regular Mode"), 0);
|
||||
popup->add_item(TTRC("Run in XR Mode"), 1);
|
||||
popup->connect(SceneStringName(id_pressed), callable_mp(this, &EditorRunBar::_play_custom_pressed));
|
||||
play_custom_scene_button = menu_button;
|
||||
} else {
|
||||
play_custom_scene_button = memnew(Button);
|
||||
play_custom_scene_button->set_toggle_mode(true);
|
||||
play_custom_scene_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::_play_custom_pressed).bind(-1));
|
||||
}
|
||||
main_hbox->add_child(play_custom_scene_button);
|
||||
play_custom_scene_button->set_theme_type_variation("RunBarButton");
|
||||
play_custom_scene_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
|
||||
play_custom_scene_button->set_tooltip_text(TTRC("Run a specific scene."));
|
||||
|
||||
ED_SHORTCUT_AND_COMMAND("editor/run_specific_scene", TTRC("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(MenuButton);
|
||||
PopupMenu *write_movie_popup = write_movie_button->get_popup();
|
||||
write_movie_popup->add_check_item(TTRC("Enable Movie Maker Mode"), MOVIE_MAKER_TOGGLE);
|
||||
write_movie_popup->add_item(TTRC("Open Movie Maker Settings..."), MOVIE_MAKER_OPEN_SETTINGS);
|
||||
write_movie_popup->connect(SceneStringName(id_pressed), callable_mp(this, &EditorRunBar::_movie_maker_item_pressed));
|
||||
|
||||
write_movie_panel->add_child(write_movie_button);
|
||||
write_movie_button->set_theme_type_variation("RunBarButtonMovieMakerDisabled");
|
||||
write_movie_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
|
||||
write_movie_button->set_tooltip_text(TTRC("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->set_accessibility_name(TTRC("Enable Movie Maker Mode"));
|
||||
}
|
140
editor/run/editor_run_bar.h
Normal file
140
editor/run/editor_run_bar.h
Normal file
@@ -0,0 +1,140 @@
|
||||
/**************************************************************************/
|
||||
/* editor_run_bar.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/export/editor_export.h"
|
||||
#include "editor/run/editor_run.h"
|
||||
#include "scene/gui/margin_container.h"
|
||||
|
||||
class Button;
|
||||
class EditorRunNative;
|
||||
class MenuButton;
|
||||
class PanelContainer;
|
||||
class HBoxContainer;
|
||||
class AcceptDialog;
|
||||
|
||||
class EditorRunBar : public MarginContainer {
|
||||
GDCLASS(EditorRunBar, MarginContainer);
|
||||
|
||||
static EditorRunBar *singleton;
|
||||
|
||||
enum RunMode {
|
||||
STOPPED = 0,
|
||||
RUN_MAIN,
|
||||
RUN_CURRENT,
|
||||
RUN_CUSTOM,
|
||||
};
|
||||
|
||||
PanelContainer *main_panel = nullptr;
|
||||
HBoxContainer *main_hbox = nullptr;
|
||||
HBoxContainer *outer_hbox = nullptr;
|
||||
|
||||
Button *profiler_autostart_indicator = nullptr;
|
||||
|
||||
PanelContainer *recovery_mode_panel = nullptr;
|
||||
Button *recovery_mode_button = nullptr;
|
||||
Button *recovery_mode_reload_button = nullptr;
|
||||
AcceptDialog *recovery_mode_popup = nullptr;
|
||||
|
||||
Button *play_button = nullptr;
|
||||
Button *pause_button = nullptr;
|
||||
Button *stop_button = nullptr;
|
||||
Button *play_scene_button = nullptr;
|
||||
Button *play_custom_scene_button = nullptr;
|
||||
|
||||
EditorRun editor_run;
|
||||
EditorRunNative *run_native = nullptr;
|
||||
|
||||
enum MovieMakerMenuItem {
|
||||
MOVIE_MAKER_TOGGLE,
|
||||
MOVIE_MAKER_OPEN_SETTINGS,
|
||||
};
|
||||
PanelContainer *write_movie_panel = nullptr;
|
||||
MenuButton *write_movie_button = nullptr;
|
||||
bool movie_maker_enabled = false;
|
||||
|
||||
RunMode current_mode = RunMode::STOPPED;
|
||||
String run_custom_filename;
|
||||
String run_current_filename;
|
||||
|
||||
void _reset_play_buttons();
|
||||
void _update_play_buttons();
|
||||
|
||||
void _movie_maker_item_pressed(int p_id);
|
||||
void _write_movie_toggled(bool p_enabled);
|
||||
void _quick_run_selected(const String &p_file_path, int p_id = -1);
|
||||
|
||||
void _play_current_pressed(int p_id = -1);
|
||||
void _play_custom_pressed(int p_id = -1);
|
||||
|
||||
void _run_scene(const String &p_scene_path = "", const Vector<String> &p_run_args = Vector<String>());
|
||||
void _run_native(const Ref<EditorExportPreset> &p_preset);
|
||||
|
||||
void _profiler_autostart_indicator_pressed();
|
||||
|
||||
private:
|
||||
static Vector<String> _get_xr_mode_play_args(int p_xr_mode_id);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static EditorRunBar *get_singleton() { return singleton; }
|
||||
|
||||
void recovery_mode_show_dialog();
|
||||
void recovery_mode_reload_project();
|
||||
|
||||
void play_main_scene(bool p_from_native = false);
|
||||
void play_current_scene(bool p_reload = false, const Vector<String> &p_play_args = Vector<String>());
|
||||
void play_custom_scene(const String &p_custom, const Vector<String> &p_play_args = Vector<String>());
|
||||
|
||||
void stop_playing();
|
||||
bool is_playing() const;
|
||||
String get_playing_scene() const;
|
||||
|
||||
Error start_native_device(int p_device_id);
|
||||
|
||||
OS::ProcessID has_child_process(OS::ProcessID p_pid) const;
|
||||
void stop_child_process(OS::ProcessID p_pid);
|
||||
OS::ProcessID get_current_process() const;
|
||||
|
||||
void set_movie_maker_enabled(bool p_enabled);
|
||||
bool is_movie_maker_enabled() const;
|
||||
|
||||
void update_profiler_autostart_indicator();
|
||||
|
||||
Button *get_pause_button() { return pause_button; }
|
||||
|
||||
HBoxContainer *get_buttons_container();
|
||||
|
||||
EditorRunBar();
|
||||
};
|
223
editor/run/editor_run_native.cpp
Normal file
223
editor/run/editor_run_native.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
/**************************************************************************/
|
||||
/* editor_run_native.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_native.h"
|
||||
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/export/editor_export.h"
|
||||
#include "editor/export/editor_export_platform.h"
|
||||
#include "editor/settings/editor_settings.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
|
||||
void EditorRunNative::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
remote_debug->set_button_icon(get_editor_theme_icon(SNAME("PlayRemote")));
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_PROCESS: {
|
||||
bool changed = EditorExport::get_singleton()->poll_export_platforms() || first;
|
||||
|
||||
if (changed) {
|
||||
PopupMenu *popup = remote_debug->get_popup();
|
||||
popup->clear();
|
||||
int device_shortcut_id = 1;
|
||||
for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
|
||||
Ref<EditorExportPreset> preset = EditorExport::get_singleton()->get_export_preset(i);
|
||||
Ref<EditorExportPlatform> eep = preset->get_platform();
|
||||
if (eep.is_null()) {
|
||||
continue;
|
||||
}
|
||||
int platform_idx = -1;
|
||||
for (int j = 0; j < EditorExport::get_singleton()->get_export_platform_count(); j++) {
|
||||
if (eep->get_name() == EditorExport::get_singleton()->get_export_platform(j)->get_name()) {
|
||||
platform_idx = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int dc = MIN(eep->get_options_count(), 9000);
|
||||
String error;
|
||||
if (dc > 0 && preset->is_runnable()) {
|
||||
popup->add_icon_item(eep->get_run_icon(), eep->get_name(), -1);
|
||||
popup->set_item_disabled(-1, true);
|
||||
for (int j = 0; j < dc; j++) {
|
||||
popup->add_icon_item(eep->get_option_icon(j), eep->get_option_label(j), 10000 * platform_idx + j);
|
||||
popup->set_item_tooltip(-1, eep->get_option_tooltip(j));
|
||||
popup->set_item_indent(-1, 2);
|
||||
if (device_shortcut_id <= 4) {
|
||||
// Assign shortcuts for the first 4 devices added in the list.
|
||||
popup->set_item_shortcut(-1, ED_GET_SHORTCUT(vformat("remote_deploy/deploy_to_device_%d", device_shortcut_id)), true);
|
||||
device_shortcut_id += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (popup->get_item_count() == 0) {
|
||||
remote_debug->set_disabled(true);
|
||||
remote_debug->set_tooltip_text(TTRC("No Remote Deploy export presets configured."));
|
||||
} else {
|
||||
remote_debug->set_disabled(false);
|
||||
remote_debug->set_tooltip_text(TTRC("Remote Deploy"));
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRunNative::_confirm_run_native() {
|
||||
run_confirmed = true;
|
||||
resume_run_native();
|
||||
}
|
||||
|
||||
Error EditorRunNative::start_run_native(int p_id) {
|
||||
if (p_id < 0) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
int platform = p_id / 10000;
|
||||
int idx = p_id % 10000;
|
||||
resume_id = p_id;
|
||||
|
||||
if (!EditorNode::get_singleton()->ensure_main_scene(true)) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
Ref<EditorExportPlatform> eep = EditorExport::get_singleton()->get_export_platform(platform);
|
||||
ERR_FAIL_COND_V(eep.is_null(), ERR_UNAVAILABLE);
|
||||
|
||||
Ref<EditorExportPreset> preset;
|
||||
|
||||
for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
|
||||
Ref<EditorExportPreset> ep = EditorExport::get_singleton()->get_export_preset(i);
|
||||
if (ep->is_runnable() && ep->get_platform() == eep) {
|
||||
preset = ep;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (preset.is_null()) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("No runnable export preset found for this platform.\nPlease add a runnable preset in the Export menu or define an existing preset as runnable."));
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
String architecture = eep->get_device_architecture(idx);
|
||||
if (!run_confirmed && !architecture.is_empty()) {
|
||||
String preset_arch = "architectures/" + architecture;
|
||||
bool is_arch_enabled = preset->get(preset_arch);
|
||||
|
||||
if (!is_arch_enabled) {
|
||||
run_native_confirm->set_text(vformat(TTR("Warning: The CPU architecture \"%s\" is not active in your export preset.\n\nRun \"Remote Deploy\" anyway?"), architecture));
|
||||
run_native_confirm->popup_centered();
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
run_confirmed = false;
|
||||
|
||||
preset->update_value_overrides();
|
||||
|
||||
emit_signal(SNAME("native_run"), preset);
|
||||
|
||||
BitField<EditorExportPlatform::DebugFlags> flags = 0;
|
||||
|
||||
bool deploy_debug_remote = is_deploy_debug_remote_enabled();
|
||||
bool deploy_dumb = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_file_server", false);
|
||||
bool debug_collisions = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_collisions", false);
|
||||
bool debug_navigation = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_navigation", false);
|
||||
|
||||
if (deploy_debug_remote) {
|
||||
flags.set_flag(EditorExportPlatform::DEBUG_FLAG_REMOTE_DEBUG);
|
||||
}
|
||||
if (deploy_dumb) {
|
||||
flags.set_flag(EditorExportPlatform::DEBUG_FLAG_DUMB_CLIENT);
|
||||
}
|
||||
if (debug_collisions) {
|
||||
flags.set_flag(EditorExportPlatform::DEBUG_FLAG_VIEW_COLLISIONS);
|
||||
}
|
||||
if (debug_navigation) {
|
||||
flags.set_flag(EditorExportPlatform::DEBUG_FLAG_VIEW_NAVIGATION);
|
||||
}
|
||||
|
||||
eep->clear_messages();
|
||||
Error err = eep->run(preset, idx, flags);
|
||||
result_dialog_log->clear();
|
||||
if (eep->fill_log_messages(result_dialog_log, err)) {
|
||||
if (eep->get_worst_message_type() >= EditorExportPlatform::EXPORT_MESSAGE_ERROR) {
|
||||
result_dialog->popup_centered_ratio(0.5);
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void EditorRunNative::resume_run_native() {
|
||||
start_run_native(resume_id);
|
||||
}
|
||||
|
||||
void EditorRunNative::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("native_run", PropertyInfo(Variant::OBJECT, "preset", PROPERTY_HINT_RESOURCE_TYPE, "EditorExportPreset")));
|
||||
}
|
||||
|
||||
bool EditorRunNative::is_deploy_debug_remote_enabled() const {
|
||||
return EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_deploy_remote_debug", true);
|
||||
}
|
||||
|
||||
EditorRunNative::EditorRunNative() {
|
||||
ED_SHORTCUT("remote_deploy/deploy_to_device_1", TTRC("Deploy to First Device in List"), KeyModifierMask::SHIFT | Key::F5);
|
||||
ED_SHORTCUT_OVERRIDE("remote_deploy/deploy_to_device_1", "macos", KeyModifierMask::META | KeyModifierMask::SHIFT | Key::B);
|
||||
ED_SHORTCUT("remote_deploy/deploy_to_device_2", TTRC("Deploy to Second Device in List"));
|
||||
ED_SHORTCUT("remote_deploy/deploy_to_device_3", TTRC("Deploy to Third Device in List"));
|
||||
ED_SHORTCUT("remote_deploy/deploy_to_device_4", TTRC("Deploy to Fourth Device in List"));
|
||||
|
||||
remote_debug = memnew(MenuButton);
|
||||
remote_debug->set_flat(false);
|
||||
remote_debug->set_theme_type_variation("RunBarButton");
|
||||
remote_debug->get_popup()->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
remote_debug->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &EditorRunNative::start_run_native));
|
||||
remote_debug->set_tooltip_text(TTRC("Remote Deploy"));
|
||||
remote_debug->set_disabled(true);
|
||||
|
||||
add_child(remote_debug);
|
||||
|
||||
result_dialog = memnew(AcceptDialog);
|
||||
result_dialog->set_title(TTR("Project Run"));
|
||||
result_dialog_log = memnew(RichTextLabel);
|
||||
result_dialog_log->set_custom_minimum_size(Size2(300, 80) * EDSCALE);
|
||||
result_dialog->add_child(result_dialog_log);
|
||||
|
||||
add_child(result_dialog);
|
||||
result_dialog->hide();
|
||||
|
||||
run_native_confirm = memnew(ConfirmationDialog);
|
||||
add_child(run_native_confirm);
|
||||
run_native_confirm->connect(SceneStringName(confirmed), callable_mp(this, &EditorRunNative::_confirm_run_native));
|
||||
|
||||
set_process(true);
|
||||
}
|
64
editor/run/editor_run_native.h
Normal file
64
editor/run/editor_run_native.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/**************************************************************************/
|
||||
/* editor_run_native.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/menu_button.h"
|
||||
#include "scene/gui/rich_text_label.h"
|
||||
|
||||
class EditorRunNative : public HBoxContainer {
|
||||
GDCLASS(EditorRunNative, HBoxContainer);
|
||||
|
||||
RichTextLabel *result_dialog_log = nullptr;
|
||||
AcceptDialog *result_dialog = nullptr;
|
||||
ConfirmationDialog *run_native_confirm = nullptr;
|
||||
bool run_confirmed = false;
|
||||
|
||||
MenuButton *remote_debug = nullptr;
|
||||
bool first = true;
|
||||
|
||||
int resume_id = -1;
|
||||
|
||||
void _confirm_run_native();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
Error start_run_native(int p_id);
|
||||
void resume_run_native();
|
||||
|
||||
bool is_deploy_debug_remote_enabled() const;
|
||||
|
||||
EditorRunNative();
|
||||
};
|
458
editor/run/embedded_process.cpp
Normal file
458
editor/run/embedded_process.cpp
Normal file
@@ -0,0 +1,458 @@
|
||||
/**************************************************************************/
|
||||
/* embedded_process.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 "embedded_process.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "scene/main/window.h"
|
||||
#include "scene/resources/style_box_flat.h"
|
||||
#include "scene/theme/theme_db.h"
|
||||
|
||||
void EmbeddedProcessBase::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_READY: {
|
||||
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &EmbeddedProcessBase::_project_settings_changed));
|
||||
} break;
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
window = get_window();
|
||||
transp_enabled = GLOBAL_GET("display/window/per_pixel_transparency/allowed");
|
||||
clear_color = GLOBAL_GET("rendering/environment/defaults/default_clear_color");
|
||||
} break;
|
||||
case NOTIFICATION_DRAW: {
|
||||
_draw();
|
||||
} break;
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
checkerboard = get_editor_theme_icon(SNAME("Checkerboard"));
|
||||
focus_style_box = get_theme_stylebox(SNAME("FocusViewport"), EditorStringName(EditorStyles));
|
||||
Ref<StyleBoxFlat> focus_style_box_flat = focus_style_box;
|
||||
if (focus_style_box_flat.is_valid()) {
|
||||
margin_top_left = Point2i(focus_style_box_flat->get_border_width(SIDE_LEFT), focus_style_box_flat->get_border_width(SIDE_TOP));
|
||||
margin_bottom_right = Point2i(focus_style_box_flat->get_border_width(SIDE_RIGHT), focus_style_box_flat->get_border_width(SIDE_BOTTOM));
|
||||
} else if (focus_style_box.is_valid()) {
|
||||
margin_top_left = Point2i(focus_style_box->get_margin(SIDE_LEFT), focus_style_box->get_margin(SIDE_TOP));
|
||||
margin_bottom_right = Point2i(focus_style_box->get_margin(SIDE_RIGHT), focus_style_box->get_margin(SIDE_BOTTOM));
|
||||
} else {
|
||||
margin_top_left = Point2i();
|
||||
margin_bottom_right = Point2i();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EmbeddedProcessBase::_project_settings_changed() {
|
||||
transp_enabled = GLOBAL_GET("display/window/per_pixel_transparency/allowed");
|
||||
clear_color = GLOBAL_GET("rendering/environment/defaults/default_clear_color");
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
void EmbeddedProcessBase::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("embedding_completed"));
|
||||
ADD_SIGNAL(MethodInfo("embedding_failed"));
|
||||
ADD_SIGNAL(MethodInfo("embedded_process_updated"));
|
||||
ADD_SIGNAL(MethodInfo("embedded_process_focused"));
|
||||
}
|
||||
|
||||
void EmbeddedProcessBase::_draw() {
|
||||
if (is_embedding_completed()) {
|
||||
Rect2 r = get_adjusted_embedded_window_rect(get_rect());
|
||||
#ifndef MACOS_ENABLED
|
||||
r.position -= get_window()->get_position();
|
||||
#endif
|
||||
if (transp_enabled) {
|
||||
draw_texture_rect(checkerboard, r, true);
|
||||
} else {
|
||||
draw_rect(r, clear_color, true);
|
||||
}
|
||||
}
|
||||
if (is_process_focused() && focus_style_box.is_valid()) {
|
||||
Size2 size = get_size();
|
||||
Rect2 r = Rect2(Point2(), size);
|
||||
focus_style_box->draw(get_canvas_item(), r);
|
||||
}
|
||||
}
|
||||
|
||||
void EmbeddedProcessBase::set_window_size(const Size2i &p_window_size) {
|
||||
if (window_size != p_window_size) {
|
||||
window_size = p_window_size;
|
||||
queue_update_embedded_process();
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void EmbeddedProcessBase::set_keep_aspect(bool p_keep_aspect) {
|
||||
if (keep_aspect != p_keep_aspect) {
|
||||
keep_aspect = p_keep_aspect;
|
||||
queue_update_embedded_process();
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
Rect2i EmbeddedProcessBase::get_screen_embedded_window_rect() const {
|
||||
return get_adjusted_embedded_window_rect(get_global_rect());
|
||||
}
|
||||
|
||||
int EmbeddedProcessBase::get_margin_size(Side p_side) const {
|
||||
ERR_FAIL_INDEX_V((int)p_side, 4, 0);
|
||||
|
||||
switch (p_side) {
|
||||
case SIDE_LEFT:
|
||||
return margin_top_left.x;
|
||||
case SIDE_RIGHT:
|
||||
return margin_bottom_right.x;
|
||||
case SIDE_TOP:
|
||||
return margin_top_left.y;
|
||||
case SIDE_BOTTOM:
|
||||
return margin_bottom_right.y;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Size2 EmbeddedProcessBase::get_margins_size() const {
|
||||
return margin_top_left + margin_bottom_right;
|
||||
}
|
||||
|
||||
EmbeddedProcessBase::EmbeddedProcessBase() {
|
||||
set_focus_mode(FOCUS_ALL);
|
||||
}
|
||||
|
||||
EmbeddedProcessBase::~EmbeddedProcessBase() {
|
||||
}
|
||||
|
||||
Rect2i EmbeddedProcess::get_adjusted_embedded_window_rect(const Rect2i &p_rect) const {
|
||||
Rect2i control_rect = Rect2i(p_rect.position + margin_top_left, (p_rect.size - get_margins_size()).maxi(1));
|
||||
if (window) {
|
||||
control_rect.position += window->get_position();
|
||||
}
|
||||
if (window_size != Size2i()) {
|
||||
Rect2i desired_rect;
|
||||
if (!keep_aspect && control_rect.size.x >= window_size.x && control_rect.size.y >= window_size.y) {
|
||||
// Fixed at the desired size.
|
||||
desired_rect.size = window_size;
|
||||
} else {
|
||||
float ratio = MIN((float)control_rect.size.x / window_size.x, (float)control_rect.size.y / window_size.y);
|
||||
desired_rect.size = Size2i(window_size.x * ratio, window_size.y * ratio).maxi(1);
|
||||
}
|
||||
desired_rect.position = Size2i(control_rect.position.x + ((control_rect.size.x - desired_rect.size.x) / 2), control_rect.position.y + ((control_rect.size.y - desired_rect.size.y) / 2));
|
||||
return desired_rect;
|
||||
} else {
|
||||
// Stretch, use all the control area.
|
||||
return control_rect;
|
||||
}
|
||||
}
|
||||
|
||||
bool EmbeddedProcess::is_embedding_in_progress() const {
|
||||
return !timer_embedding->is_stopped();
|
||||
}
|
||||
|
||||
bool EmbeddedProcess::is_embedding_completed() const {
|
||||
return embedding_completed;
|
||||
}
|
||||
|
||||
bool EmbeddedProcess::is_process_focused() const {
|
||||
return focused_process_id == current_process_id && has_focus();
|
||||
}
|
||||
|
||||
int EmbeddedProcess::get_embedded_pid() const {
|
||||
return current_process_id;
|
||||
}
|
||||
|
||||
void EmbeddedProcess::embed_process(OS::ProcessID p_pid) {
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_MSG(!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING), "Embedded process not supported by this display server.");
|
||||
|
||||
if (current_process_id != 0) {
|
||||
// Stop embedding the last process.
|
||||
OS::get_singleton()->kill(current_process_id);
|
||||
}
|
||||
|
||||
reset();
|
||||
|
||||
current_process_id = p_pid;
|
||||
start_embedding_time = OS::get_singleton()->get_ticks_msec();
|
||||
embedding_grab_focus = has_focus();
|
||||
timer_update_embedded_process->start();
|
||||
set_process(true);
|
||||
set_notify_transform(true);
|
||||
|
||||
// Attempt to embed the process, but if it has just started and the window is not ready yet,
|
||||
// we will retry in this case.
|
||||
_try_embed_process();
|
||||
}
|
||||
|
||||
void EmbeddedProcess::reset() {
|
||||
if (current_process_id != 0 && embedding_completed) {
|
||||
DisplayServer::get_singleton()->remove_embedded_process(current_process_id);
|
||||
}
|
||||
current_process_id = 0;
|
||||
embedding_completed = false;
|
||||
start_embedding_time = 0;
|
||||
embedding_grab_focus = false;
|
||||
timer_embedding->stop();
|
||||
timer_update_embedded_process->stop();
|
||||
set_process(false);
|
||||
set_notify_transform(false);
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
void EmbeddedProcess::request_close() {
|
||||
if (current_process_id != 0 && embedding_completed) {
|
||||
DisplayServer::get_singleton()->request_close_embedded_process(current_process_id);
|
||||
}
|
||||
}
|
||||
|
||||
void EmbeddedProcess::_try_embed_process() {
|
||||
bool is_visible = is_visible_in_tree();
|
||||
Error err = DisplayServer::get_singleton()->embed_process(window->get_window_id(), current_process_id, get_screen_embedded_window_rect(), is_visible, is_visible && application_has_focus && embedding_grab_focus);
|
||||
if (err == OK) {
|
||||
embedding_completed = true;
|
||||
queue_redraw();
|
||||
emit_signal(SNAME("embedding_completed"));
|
||||
} else if (err == ERR_DOES_NOT_EXIST) {
|
||||
if (OS::get_singleton()->get_ticks_msec() - start_embedding_time >= (uint64_t)embedding_timeout) {
|
||||
// Embedding process timed out.
|
||||
reset();
|
||||
emit_signal(SNAME("embedding_failed"));
|
||||
} else {
|
||||
// Tries another shot.
|
||||
timer_embedding->start();
|
||||
}
|
||||
} else {
|
||||
// Another unknown error.
|
||||
reset();
|
||||
emit_signal(SNAME("embedding_failed"));
|
||||
}
|
||||
}
|
||||
|
||||
bool EmbeddedProcess::_is_embedded_process_updatable() {
|
||||
return window && current_process_id != 0 && embedding_completed;
|
||||
}
|
||||
|
||||
void EmbeddedProcess::queue_update_embedded_process() {
|
||||
updated_embedded_process_queued = true;
|
||||
}
|
||||
|
||||
void EmbeddedProcess::_timer_update_embedded_process_timeout() {
|
||||
_check_focused_process_id();
|
||||
_check_mouse_over();
|
||||
|
||||
if (!updated_embedded_process_queued) {
|
||||
// We need to detect when the control globally changes location or size on the screen.
|
||||
// NOTIFICATION_RESIZED and NOTIFICATION_WM_POSITION_CHANGED are not enough to detect
|
||||
// resized parent to siblings controls that can affect global position.
|
||||
Rect2i new_global_rect = get_global_rect();
|
||||
if (last_global_rect != new_global_rect) {
|
||||
last_global_rect = new_global_rect;
|
||||
queue_update_embedded_process();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EmbeddedProcess::_update_embedded_process() {
|
||||
if (!_is_embedded_process_updatable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool must_grab_focus = false;
|
||||
bool focus = has_focus();
|
||||
if (last_updated_embedded_process_focused != focus) {
|
||||
if (focus) {
|
||||
must_grab_focus = true;
|
||||
}
|
||||
last_updated_embedded_process_focused = focus;
|
||||
}
|
||||
|
||||
DisplayServer::get_singleton()->embed_process(window->get_window_id(), current_process_id, get_screen_embedded_window_rect(), is_visible_in_tree(), must_grab_focus);
|
||||
emit_signal(SNAME("embedded_process_updated"));
|
||||
}
|
||||
|
||||
void EmbeddedProcess::_timer_embedding_timeout() {
|
||||
_try_embed_process();
|
||||
}
|
||||
|
||||
void EmbeddedProcess::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_PROCESS: {
|
||||
if (updated_embedded_process_queued) {
|
||||
updated_embedded_process_queued = false;
|
||||
_update_embedded_process();
|
||||
}
|
||||
} break;
|
||||
case NOTIFICATION_RESIZED:
|
||||
case NOTIFICATION_VISIBILITY_CHANGED:
|
||||
case NOTIFICATION_WM_POSITION_CHANGED: {
|
||||
queue_update_embedded_process();
|
||||
} break;
|
||||
case NOTIFICATION_APPLICATION_FOCUS_IN: {
|
||||
application_has_focus = true;
|
||||
last_application_focus_time = OS::get_singleton()->get_ticks_msec();
|
||||
} break;
|
||||
case NOTIFICATION_APPLICATION_FOCUS_OUT: {
|
||||
application_has_focus = false;
|
||||
} break;
|
||||
case NOTIFICATION_FOCUS_ENTER: {
|
||||
queue_update_embedded_process();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EmbeddedProcess::_check_mouse_over() {
|
||||
// This method checks if the mouse is over the embedded process while the current application is focused.
|
||||
// The goal is to give focus to the embedded process as soon as the mouse hovers over it,
|
||||
// allowing the user to interact with it immediately without needing to click first.
|
||||
if (!embedding_completed || !application_has_focus || !window || has_focus() || !is_visible_in_tree() || !window->has_focus() || Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) || Input::get_singleton()->is_mouse_button_pressed(MouseButton::RIGHT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Before checking whether the mouse is truly inside the embedded process, ensure
|
||||
// the editor has enough time to re-render. When a breakpoint is hit in the script editor,
|
||||
// `_check_mouse_over` may be triggered before the editor hides the game workspace.
|
||||
// This prevents the embedded process from regaining focus immediately after the editor has taken it.
|
||||
if (OS::get_singleton()->get_ticks_msec() - last_application_focus_time < 500) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Input::is_mouse_button_pressed is not sufficient to detect the mouse button state
|
||||
// while the floating game window is being resized.
|
||||
BitField<MouseButtonMask> mouse_button_mask = DisplayServer::get_singleton()->mouse_get_button_state();
|
||||
if (!mouse_button_mask.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Not stealing focus from a textfield.
|
||||
if (get_viewport()->gui_get_focus_owner() && get_viewport()->gui_get_focus_owner()->is_text_field()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 mouse_position = DisplayServer::get_singleton()->mouse_get_position();
|
||||
Rect2i window_rect = get_screen_embedded_window_rect();
|
||||
if (!window_rect.has_point(mouse_position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't grab the focus if mouse over another window.
|
||||
DisplayServer::WindowID window_id_over = DisplayServer::get_singleton()->get_window_at_screen_position(mouse_position);
|
||||
if (window_id_over > 0 && window_id_over != window->get_window_id()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if there's an exclusive popup, an open menu, or a tooltip.
|
||||
// We don't want to grab focus to prevent the game window from coming to the front of the modal window
|
||||
// or the open menu from closing when the mouse cursor moves outside the menu and over the embedded game.
|
||||
Vector<DisplayServer::WindowID> wl = DisplayServer::get_singleton()->get_window_list();
|
||||
for (const DisplayServer::WindowID &window_id : wl) {
|
||||
Window *w = Window::get_from_id(window_id);
|
||||
if (w && (w->is_exclusive() || w->get_flag(Window::FLAG_POPUP))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Force "regrabbing" the game window focus.
|
||||
last_updated_embedded_process_focused = false;
|
||||
|
||||
grab_focus();
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
void EmbeddedProcess::_check_focused_process_id() {
|
||||
OS::ProcessID process_id = DisplayServer::get_singleton()->get_focused_process_id();
|
||||
if (process_id != focused_process_id) {
|
||||
focused_process_id = process_id;
|
||||
if (focused_process_id == current_process_id) {
|
||||
// The embedded process got the focus.
|
||||
|
||||
// Refocus the current model when focusing the embedded process.
|
||||
Window *modal_window = _get_current_modal_window();
|
||||
if (!modal_window) {
|
||||
emit_signal(SNAME("embedded_process_focused"));
|
||||
if (has_focus()) {
|
||||
// Redraw to updated the focus style.
|
||||
queue_redraw();
|
||||
} else {
|
||||
grab_focus();
|
||||
}
|
||||
}
|
||||
} else if (has_focus()) {
|
||||
release_focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the opened modal dialog is refocused when the focused process is the embedded process.
|
||||
if (!application_has_focus && focused_process_id == current_process_id) {
|
||||
Window *modal_window = _get_current_modal_window();
|
||||
if (modal_window) {
|
||||
if (modal_window->get_mode() == Window::MODE_MINIMIZED) {
|
||||
modal_window->set_mode(Window::MODE_WINDOWED);
|
||||
}
|
||||
callable_mp(modal_window, &Window::grab_focus).call_deferred();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Window *EmbeddedProcess::_get_current_modal_window() {
|
||||
Vector<DisplayServer::WindowID> wl = DisplayServer::get_singleton()->get_window_list();
|
||||
for (const DisplayServer::WindowID &window_id : wl) {
|
||||
Window *w = Window::get_from_id(window_id);
|
||||
if (!w) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (w->is_exclusive()) {
|
||||
return w;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
EmbeddedProcess::EmbeddedProcess() :
|
||||
EmbeddedProcessBase() {
|
||||
timer_embedding = memnew(Timer);
|
||||
timer_embedding->set_wait_time(0.1);
|
||||
timer_embedding->set_one_shot(true);
|
||||
add_child(timer_embedding);
|
||||
timer_embedding->connect("timeout", callable_mp(this, &EmbeddedProcess::_timer_embedding_timeout));
|
||||
|
||||
timer_update_embedded_process = memnew(Timer);
|
||||
timer_update_embedded_process->set_wait_time(0.1);
|
||||
add_child(timer_update_embedded_process);
|
||||
timer_update_embedded_process->connect("timeout", callable_mp(this, &EmbeddedProcess::_timer_update_embedded_process_timeout));
|
||||
}
|
||||
|
||||
EmbeddedProcess::~EmbeddedProcess() {
|
||||
if (current_process_id != 0) {
|
||||
// Stop embedding the last process.
|
||||
OS::get_singleton()->kill(current_process_id);
|
||||
reset();
|
||||
}
|
||||
}
|
128
editor/run/embedded_process.h
Normal file
128
editor/run/embedded_process.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/**************************************************************************/
|
||||
/* embedded_process.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "scene/gui/control.h"
|
||||
|
||||
class ScriptEditorDebugger;
|
||||
|
||||
class EmbeddedProcessBase : public Control {
|
||||
GDCLASS(EmbeddedProcessBase, Control);
|
||||
|
||||
void _draw();
|
||||
|
||||
protected:
|
||||
Ref<StyleBox> focus_style_box;
|
||||
Size2i window_size;
|
||||
bool keep_aspect = false;
|
||||
Point2i margin_top_left;
|
||||
Point2i margin_bottom_right;
|
||||
Window *window = nullptr;
|
||||
|
||||
bool transp_enabled = false;
|
||||
Color clear_color;
|
||||
Ref<Texture2D> checkerboard;
|
||||
|
||||
void _project_settings_changed();
|
||||
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
virtual void set_script_debugger(ScriptEditorDebugger *p_debugger) {}
|
||||
|
||||
virtual bool is_embedding_completed() const = 0;
|
||||
virtual bool is_embedding_in_progress() const = 0;
|
||||
virtual bool is_process_focused() const = 0;
|
||||
virtual void embed_process(OS::ProcessID p_pid) = 0;
|
||||
virtual int get_embedded_pid() const = 0;
|
||||
virtual void reset() = 0;
|
||||
virtual void request_close() = 0;
|
||||
virtual void queue_update_embedded_process() = 0;
|
||||
|
||||
void set_window_size(const Size2i &p_window_size);
|
||||
void set_keep_aspect(bool p_keep_aspect);
|
||||
virtual Rect2i get_adjusted_embedded_window_rect(const Rect2i &p_rect) const = 0;
|
||||
Rect2i get_screen_embedded_window_rect() const;
|
||||
int get_margin_size(Side p_side) const;
|
||||
Size2 get_margins_size() const;
|
||||
|
||||
EmbeddedProcessBase();
|
||||
virtual ~EmbeddedProcessBase();
|
||||
};
|
||||
|
||||
class EmbeddedProcess : public EmbeddedProcessBase {
|
||||
GDCLASS(EmbeddedProcess, EmbeddedProcessBase);
|
||||
|
||||
bool application_has_focus = true;
|
||||
uint64_t last_application_focus_time = 0;
|
||||
OS::ProcessID focused_process_id = 0;
|
||||
OS::ProcessID current_process_id = 0;
|
||||
bool embedding_grab_focus = false;
|
||||
bool embedding_completed = false;
|
||||
uint64_t start_embedding_time = 0;
|
||||
bool updated_embedded_process_queued = false;
|
||||
bool last_updated_embedded_process_focused = false;
|
||||
|
||||
Timer *timer_embedding = nullptr;
|
||||
Timer *timer_update_embedded_process = nullptr;
|
||||
|
||||
const int embedding_timeout = 45000;
|
||||
|
||||
Rect2i last_global_rect;
|
||||
|
||||
void _try_embed_process();
|
||||
void _update_embedded_process();
|
||||
void _timer_embedding_timeout();
|
||||
void _timer_update_embedded_process_timeout();
|
||||
void _check_mouse_over();
|
||||
void _check_focused_process_id();
|
||||
bool _is_embedded_process_updatable();
|
||||
Window *_get_current_modal_window();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
bool is_embedding_in_progress() const override;
|
||||
bool is_embedding_completed() const override;
|
||||
bool is_process_focused() const override;
|
||||
void embed_process(OS::ProcessID p_pid) override;
|
||||
int get_embedded_pid() const override;
|
||||
void reset() override;
|
||||
void request_close() override;
|
||||
void queue_update_embedded_process() override;
|
||||
|
||||
Rect2i get_adjusted_embedded_window_rect(const Rect2i &p_rect) const override;
|
||||
|
||||
EmbeddedProcess();
|
||||
~EmbeddedProcess() override;
|
||||
};
|
1362
editor/run/game_view_plugin.cpp
Normal file
1362
editor/run/game_view_plugin.cpp
Normal file
File diff suppressed because it is too large
Load Diff
285
editor/run/game_view_plugin.h
Normal file
285
editor/run/game_view_plugin.h
Normal file
@@ -0,0 +1,285 @@
|
||||
/**************************************************************************/
|
||||
/* game_view_plugin.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/debugger/editor_debugger_node.h"
|
||||
#include "editor/debugger/editor_debugger_plugin.h"
|
||||
#include "editor/editor_main_screen.h"
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
#include "scene/debugger/scene_debugger.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
|
||||
class EmbeddedProcessBase;
|
||||
class VSeparator;
|
||||
class WindowWrapper;
|
||||
class ScriptEditorDebugger;
|
||||
|
||||
class GameViewDebugger : public EditorDebuggerPlugin {
|
||||
GDCLASS(GameViewDebugger, EditorDebuggerPlugin);
|
||||
|
||||
private:
|
||||
Vector<Ref<EditorDebuggerSession>> sessions;
|
||||
|
||||
bool is_feature_enabled = true;
|
||||
int node_type = RuntimeNodeSelect::NODE_TYPE_NONE;
|
||||
bool selection_visible = true;
|
||||
int select_mode = RuntimeNodeSelect::SELECT_MODE_SINGLE;
|
||||
bool mute_audio = false;
|
||||
EditorDebuggerNode::CameraOverride camera_override_mode = EditorDebuggerNode::OVERRIDE_INGAME;
|
||||
|
||||
void _session_started(Ref<EditorDebuggerSession> p_session);
|
||||
void _session_stopped();
|
||||
|
||||
void _feature_profile_changed();
|
||||
|
||||
struct ScreenshotCB {
|
||||
Callable cb;
|
||||
Rect2i rect;
|
||||
};
|
||||
|
||||
int64_t scr_rq_id = 0;
|
||||
HashMap<uint64_t, ScreenshotCB> screenshot_callbacks;
|
||||
|
||||
bool _msg_get_screenshot(const Array &p_args);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual bool capture(const String &p_message, const Array &p_data, int p_session) override;
|
||||
virtual bool has_capture(const String &p_capture) const override;
|
||||
|
||||
bool add_screenshot_callback(const Callable &p_callaback, const Rect2i &p_rect);
|
||||
|
||||
void set_suspend(bool p_enabled);
|
||||
void next_frame();
|
||||
|
||||
void set_node_type(int p_type);
|
||||
void set_select_mode(int p_mode);
|
||||
|
||||
void set_selection_visible(bool p_visible);
|
||||
|
||||
void set_debug_mute_audio(bool p_enabled);
|
||||
|
||||
void set_camera_override(bool p_enabled);
|
||||
void set_camera_manipulate_mode(EditorDebuggerNode::CameraOverride p_mode);
|
||||
|
||||
void reset_camera_2d_position();
|
||||
void reset_camera_3d_position();
|
||||
|
||||
virtual void setup_session(int p_session_id) override;
|
||||
|
||||
GameViewDebugger();
|
||||
};
|
||||
|
||||
class GameView : public VBoxContainer {
|
||||
GDCLASS(GameView, VBoxContainer);
|
||||
|
||||
enum {
|
||||
CAMERA_RESET_2D,
|
||||
CAMERA_RESET_3D,
|
||||
CAMERA_MODE_INGAME,
|
||||
CAMERA_MODE_EDITORS,
|
||||
EMBED_RUN_GAME_EMBEDDED,
|
||||
EMBED_MAKE_FLOATING_ON_PLAY,
|
||||
};
|
||||
|
||||
enum EmbedSizeMode {
|
||||
SIZE_MODE_FIXED,
|
||||
SIZE_MODE_KEEP_ASPECT,
|
||||
SIZE_MODE_STRETCH,
|
||||
};
|
||||
|
||||
enum EmbedAvailability {
|
||||
EMBED_AVAILABLE,
|
||||
EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED,
|
||||
EMBED_NOT_AVAILABLE_MINIMIZED,
|
||||
EMBED_NOT_AVAILABLE_MAXIMIZED,
|
||||
EMBED_NOT_AVAILABLE_FULLSCREEN,
|
||||
EMBED_NOT_AVAILABLE_SINGLE_WINDOW_MODE,
|
||||
EMBED_NOT_AVAILABLE_PROJECT_DISPLAY_DRIVER,
|
||||
};
|
||||
|
||||
inline static GameView *singleton = nullptr;
|
||||
|
||||
Ref<GameViewDebugger> debugger;
|
||||
WindowWrapper *window_wrapper = nullptr;
|
||||
|
||||
bool is_feature_enabled = true;
|
||||
int active_sessions = 0;
|
||||
int screen_index_before_start = -1;
|
||||
ScriptEditorDebugger *embedded_script_debugger = nullptr;
|
||||
|
||||
bool embed_on_play = true;
|
||||
bool make_floating_on_play = true;
|
||||
EmbedSizeMode embed_size_mode = SIZE_MODE_FIXED;
|
||||
bool paused = false;
|
||||
Size2 size_paused;
|
||||
|
||||
Rect2i floating_window_rect;
|
||||
int floating_window_screen = -1;
|
||||
|
||||
bool debug_mute_audio = false;
|
||||
|
||||
Button *suspend_button = nullptr;
|
||||
Button *next_frame_button = nullptr;
|
||||
|
||||
Button *node_type_button[RuntimeNodeSelect::NODE_TYPE_MAX];
|
||||
Button *select_mode_button[RuntimeNodeSelect::SELECT_MODE_MAX];
|
||||
|
||||
Button *hide_selection = nullptr;
|
||||
|
||||
Button *debug_mute_audio_button = nullptr;
|
||||
|
||||
Button *camera_override_button = nullptr;
|
||||
MenuButton *camera_override_menu = nullptr;
|
||||
|
||||
VSeparator *embedding_separator = nullptr;
|
||||
Button *fixed_size_button = nullptr;
|
||||
Button *keep_aspect_button = nullptr;
|
||||
Button *stretch_button = nullptr;
|
||||
MenuButton *embed_options_menu = nullptr;
|
||||
Label *game_size_label = nullptr;
|
||||
Panel *panel = nullptr;
|
||||
EmbeddedProcessBase *embedded_process = nullptr;
|
||||
Label *state_label = nullptr;
|
||||
|
||||
void _sessions_changed();
|
||||
|
||||
void _update_debugger_buttons();
|
||||
|
||||
void _handle_shortcut_requested(int p_embed_action);
|
||||
void _toggle_suspend_button();
|
||||
void _suspend_button_toggled(bool p_pressed);
|
||||
|
||||
void _node_type_pressed(int p_option);
|
||||
void _select_mode_pressed(int p_option);
|
||||
void _embed_options_menu_menu_id_pressed(int p_id);
|
||||
void _size_mode_button_pressed(int size_mode);
|
||||
|
||||
void _play_pressed();
|
||||
static void _instance_starting_static(int p_idx, List<String> &r_arguments);
|
||||
void _instance_starting(int p_idx, List<String> &r_arguments);
|
||||
static bool _instance_rq_screenshot_static(const Callable &p_callback);
|
||||
bool _instance_rq_screenshot(const Callable &p_callback);
|
||||
void _stop_pressed();
|
||||
void _embedding_completed();
|
||||
void _embedding_failed();
|
||||
void _embedded_process_updated();
|
||||
void _embedded_process_focused();
|
||||
void _editor_or_project_settings_changed();
|
||||
|
||||
EmbedAvailability _get_embed_available();
|
||||
void _update_ui();
|
||||
void _update_embed_menu_options();
|
||||
void _update_embed_window_size();
|
||||
void _update_arguments_for_instance(int p_idx, List<String> &r_arguments);
|
||||
void _show_update_window_wrapper();
|
||||
|
||||
void _hide_selection_toggled(bool p_pressed);
|
||||
|
||||
void _debug_mute_audio_button_pressed();
|
||||
|
||||
void _camera_override_button_toggled(bool p_pressed);
|
||||
void _camera_override_menu_id_pressed(int p_id);
|
||||
|
||||
void _window_close_request();
|
||||
void _update_floating_window_settings();
|
||||
void _attach_script_debugger();
|
||||
void _detach_script_debugger();
|
||||
void _remote_window_title_changed(String title);
|
||||
|
||||
void _debugger_breaked(bool p_breaked, bool p_can_debug);
|
||||
|
||||
void _feature_profile_changed();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void set_state(const Dictionary &p_state);
|
||||
Dictionary get_state() const;
|
||||
|
||||
void set_window_layout(Ref<ConfigFile> p_layout);
|
||||
void get_window_layout(Ref<ConfigFile> p_layout);
|
||||
|
||||
GameView(Ref<GameViewDebugger> p_debugger, EmbeddedProcessBase *p_embedded_process, WindowWrapper *p_wrapper);
|
||||
};
|
||||
|
||||
class GameViewPluginBase : public EditorPlugin {
|
||||
GDCLASS(GameViewPluginBase, EditorPlugin);
|
||||
|
||||
#ifndef ANDROID_ENABLED
|
||||
GameView *game_view = nullptr;
|
||||
WindowWrapper *window_wrapper = nullptr;
|
||||
#endif // ANDROID_ENABLED
|
||||
|
||||
Ref<GameViewDebugger> debugger;
|
||||
|
||||
String last_editor;
|
||||
|
||||
#ifndef ANDROID_ENABLED
|
||||
void _window_visibility_changed(bool p_visible);
|
||||
#endif // ANDROID_ENABLED
|
||||
void _save_last_editor(const String &p_editor);
|
||||
void _focus_another_editor();
|
||||
bool _is_window_wrapper_enabled() const;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
#ifndef ANDROID_ENABLED
|
||||
void setup(Ref<GameViewDebugger> p_debugger, EmbeddedProcessBase *p_embedded_process);
|
||||
#endif
|
||||
|
||||
public:
|
||||
virtual String get_plugin_name() const override { return TTRC("Game"); }
|
||||
bool has_main_screen() const override { return true; }
|
||||
virtual void edit(Object *p_object) override {}
|
||||
virtual bool handles(Object *p_object) const override { return false; }
|
||||
virtual void selected_notify() override;
|
||||
|
||||
Ref<GameViewDebugger> get_debugger() const { return debugger; }
|
||||
|
||||
#ifndef ANDROID_ENABLED
|
||||
virtual void make_visible(bool p_visible) override;
|
||||
|
||||
virtual void set_window_layout(Ref<ConfigFile> p_layout) override;
|
||||
virtual void get_window_layout(Ref<ConfigFile> p_layout) override;
|
||||
#endif // ANDROID_ENABLED
|
||||
GameViewPluginBase();
|
||||
};
|
||||
|
||||
class GameViewPlugin : public GameViewPluginBase {
|
||||
GDCLASS(GameViewPlugin, GameViewPluginBase);
|
||||
|
||||
public:
|
||||
GameViewPlugin();
|
||||
};
|
413
editor/run/run_instances_dialog.cpp
Normal file
413
editor/run/run_instances_dialog.cpp
Normal file
@@ -0,0 +1,413 @@
|
||||
/**************************************************************************/
|
||||
/* run_instances_dialog.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 "run_instances_dialog.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "editor/settings/editor_settings.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/check_box.h"
|
||||
#include "scene/gui/grid_container.h"
|
||||
#include "scene/gui/label.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
#include "scene/gui/popup_menu.h"
|
||||
#include "scene/gui/separator.h"
|
||||
#include "scene/gui/spin_box.h"
|
||||
#include "scene/gui/tree.h"
|
||||
#include "scene/main/timer.h"
|
||||
|
||||
void RunInstancesDialog::_fetch_main_args() {
|
||||
if (!main_args_edit->has_focus()) { // Only set the text if the user is not currently editing it.
|
||||
main_args_edit->set_text(GLOBAL_GET("editor/run/main_run_args"));
|
||||
}
|
||||
}
|
||||
|
||||
void RunInstancesDialog::_start_main_timer() {
|
||||
main_apply_timer->start();
|
||||
}
|
||||
|
||||
void RunInstancesDialog::_start_instance_timer() {
|
||||
instance_apply_timer->start();
|
||||
}
|
||||
|
||||
void RunInstancesDialog::_refresh_argument_count() {
|
||||
instance_tree->clear();
|
||||
instance_tree->create_item(); // Root.
|
||||
|
||||
while (instance_count->get_value() > stored_data.size()) {
|
||||
stored_data.append(Dictionary());
|
||||
}
|
||||
|
||||
instances_data.resize(instance_count->get_value());
|
||||
InstanceData *instances_write = instances_data.ptrw();
|
||||
|
||||
for (int i = 0; i < instances_data.size(); i++) {
|
||||
InstanceData instance;
|
||||
const Dictionary &instance_data = stored_data[i];
|
||||
|
||||
_create_instance(instance, instance_data, i + 1);
|
||||
instances_write[i] = instance;
|
||||
}
|
||||
}
|
||||
|
||||
void RunInstancesDialog::_create_instance(InstanceData &p_instance, const Dictionary &p_data, int p_idx) {
|
||||
TreeItem *instance_item = instance_tree->create_item();
|
||||
p_instance.item = instance_item;
|
||||
|
||||
instance_item->set_cell_mode(COLUMN_OVERRIDE_ARGS, TreeItem::CELL_MODE_CHECK);
|
||||
instance_item->set_editable(COLUMN_OVERRIDE_ARGS, true);
|
||||
instance_item->set_text(COLUMN_OVERRIDE_ARGS, TTR("Enabled"));
|
||||
instance_item->set_checked(COLUMN_OVERRIDE_ARGS, p_data.get("override_args", false));
|
||||
|
||||
instance_item->set_editable(COLUMN_LAUNCH_ARGUMENTS, true);
|
||||
instance_item->set_text(COLUMN_LAUNCH_ARGUMENTS, p_data.get("arguments", String()));
|
||||
|
||||
instance_item->set_cell_mode(COLUMN_OVERRIDE_FEATURES, TreeItem::CELL_MODE_CHECK);
|
||||
instance_item->set_editable(COLUMN_OVERRIDE_FEATURES, true);
|
||||
instance_item->set_text(COLUMN_OVERRIDE_FEATURES, TTR("Enabled"));
|
||||
instance_item->set_checked(COLUMN_OVERRIDE_FEATURES, p_data.get("override_features", false));
|
||||
|
||||
instance_item->set_editable(COLUMN_FEATURE_TAGS, true);
|
||||
instance_item->set_text(COLUMN_FEATURE_TAGS, p_data.get("features", String()));
|
||||
}
|
||||
|
||||
void RunInstancesDialog::_save_main_args() {
|
||||
ProjectSettings::get_singleton()->set_setting("editor/run/main_run_args", main_args_edit->get_text());
|
||||
ProjectSettings::get_singleton()->save();
|
||||
EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_main_feature_tags", main_features_edit->get_text());
|
||||
EditorSettings::get_singleton()->set_project_metadata("debug_options", "multiple_instances_enabled", enable_multiple_instances_checkbox->is_pressed());
|
||||
}
|
||||
|
||||
void RunInstancesDialog::_save_arguments() {
|
||||
for (int i = 0; i < instances_data.size(); i++) {
|
||||
const InstanceData &instance = instances_data[i];
|
||||
Dictionary dict;
|
||||
dict["override_args"] = instance.overrides_run_args();
|
||||
dict["arguments"] = instance.get_launch_arguments();
|
||||
dict["override_features"] = instance.overrides_features();
|
||||
dict["features"] = instance.get_feature_tags();
|
||||
stored_data[i] = dict;
|
||||
}
|
||||
EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_instances_config", stored_data);
|
||||
EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_instance_count", instance_count->get_value());
|
||||
}
|
||||
|
||||
Vector<String> RunInstancesDialog::_split_cmdline_args(const String &p_arg_string) const {
|
||||
Vector<String> split_args;
|
||||
int arg_start = 0;
|
||||
bool is_quoted = false;
|
||||
char32_t quote_char = '-';
|
||||
char32_t arg_char;
|
||||
int arg_length;
|
||||
for (int i = 0; i < p_arg_string.length(); i++) {
|
||||
arg_char = p_arg_string[i];
|
||||
if (arg_char == '\"' || arg_char == '\'') {
|
||||
if (i == 0 || p_arg_string[i - 1] != '\\') {
|
||||
if (is_quoted) {
|
||||
if (arg_char == quote_char) {
|
||||
is_quoted = false;
|
||||
quote_char = '-';
|
||||
}
|
||||
} else {
|
||||
is_quoted = true;
|
||||
quote_char = arg_char;
|
||||
}
|
||||
}
|
||||
} else if (!is_quoted && arg_char == ' ') {
|
||||
arg_length = i - arg_start;
|
||||
if (arg_length > 0) {
|
||||
split_args.push_back(p_arg_string.substr(arg_start, arg_length));
|
||||
}
|
||||
arg_start = i + 1;
|
||||
}
|
||||
}
|
||||
arg_length = p_arg_string.length() - arg_start;
|
||||
if (arg_length > 0) {
|
||||
split_args.push_back(p_arg_string.substr(arg_start, arg_length));
|
||||
}
|
||||
return split_args;
|
||||
}
|
||||
|
||||
void RunInstancesDialog::_instance_menu_id_pressed(int p_option) {
|
||||
switch (p_option) {
|
||||
case CLEAR_ITEM: {
|
||||
int item_to_clear = popup_menu->get_item_metadata(0);
|
||||
if (item_to_clear >= 0 && item_to_clear < stored_data.size()) {
|
||||
stored_data[item_to_clear] = Dictionary();
|
||||
}
|
||||
} break;
|
||||
case CLEAR_ALL: {
|
||||
stored_data.clear();
|
||||
stored_data.resize(instance_count->get_value());
|
||||
} break;
|
||||
}
|
||||
|
||||
_start_instance_timer();
|
||||
_refresh_argument_count();
|
||||
}
|
||||
|
||||
void RunInstancesDialog::_instance_tree_rmb(const Vector2 &p_pos, MouseButton p_button) {
|
||||
if (p_button != MouseButton::RIGHT) {
|
||||
return;
|
||||
}
|
||||
|
||||
popup_menu->clear();
|
||||
popup_menu->add_item(TTR("Clear"), CLEAR_ITEM);
|
||||
|
||||
TreeItem *item = instance_tree->get_item_at_position(p_pos);
|
||||
if (item) {
|
||||
popup_menu->set_item_metadata(0, item->get_index());
|
||||
} else {
|
||||
popup_menu->set_item_disabled(0, true);
|
||||
}
|
||||
|
||||
popup_menu->add_item(TTR("Clear All"), CLEAR_ALL);
|
||||
popup_menu->set_position(instance_tree->get_screen_position() + p_pos);
|
||||
popup_menu->reset_size();
|
||||
popup_menu->popup();
|
||||
}
|
||||
|
||||
void RunInstancesDialog::popup_dialog() {
|
||||
popup_centered_clamped(Size2(1200, 600) * EDSCALE, 0.8);
|
||||
}
|
||||
|
||||
int RunInstancesDialog::get_instance_count() const {
|
||||
if (enable_multiple_instances_checkbox->is_pressed()) {
|
||||
return instance_count->get_value();
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
void RunInstancesDialog::get_argument_list_for_instance(int p_idx, List<String> &r_list) const {
|
||||
bool override_args = instances_data[p_idx].overrides_run_args();
|
||||
bool use_multiple_instances = enable_multiple_instances_checkbox->is_pressed();
|
||||
String raw_custom_args;
|
||||
|
||||
if (use_multiple_instances) {
|
||||
if (override_args) {
|
||||
raw_custom_args = instances_data[p_idx].get_launch_arguments();
|
||||
} else {
|
||||
raw_custom_args = main_args_edit->get_text() + " " + instances_data[p_idx].get_launch_arguments();
|
||||
}
|
||||
} else {
|
||||
raw_custom_args = main_args_edit->get_text();
|
||||
}
|
||||
|
||||
String exec = OS::get_singleton()->get_executable_path();
|
||||
|
||||
if (!raw_custom_args.is_empty()) {
|
||||
// Allow the user to specify a command to run, similar to Steam's launch options.
|
||||
// In this case, Godot will no longer be run directly; it's up to the underlying command
|
||||
// to run it. For instance, this can be used on Linux to force a running project
|
||||
// to use Optimus using `prime-run` or similar.
|
||||
// Example: `prime-run %command% --time-scale 0.5`
|
||||
const int placeholder_pos = raw_custom_args.find("%command%");
|
||||
|
||||
Vector<String> custom_args;
|
||||
|
||||
if (placeholder_pos != -1) {
|
||||
// Prepend executable-specific custom arguments.
|
||||
// If nothing is placed before `%command%`, behave as if no placeholder was specified.
|
||||
Vector<String> exec_args = _split_cmdline_args(raw_custom_args.substr(0, placeholder_pos));
|
||||
if (exec_args.size() > 0) {
|
||||
exec = exec_args[0];
|
||||
exec_args.remove_at(0);
|
||||
|
||||
// Append the Godot executable name before we append executable arguments
|
||||
// (since the order is reversed when using `push_front()`).
|
||||
r_list.push_front(OS::get_singleton()->get_executable_path());
|
||||
}
|
||||
|
||||
for (int i = exec_args.size() - 1; i >= 0; i--) {
|
||||
// Iterate backwards as we're pushing items in the reverse order.
|
||||
r_list.push_front(exec_args[i].replace(" ", "%20"));
|
||||
}
|
||||
|
||||
// Append Godot-specific custom arguments.
|
||||
custom_args = _split_cmdline_args(raw_custom_args.substr(placeholder_pos + String("%command%").size()));
|
||||
for (int i = 0; i < custom_args.size(); i++) {
|
||||
r_list.push_back(custom_args[i].replace(" ", "%20"));
|
||||
}
|
||||
} else {
|
||||
// Append Godot-specific custom arguments.
|
||||
custom_args = _split_cmdline_args(raw_custom_args);
|
||||
for (int i = 0; i < custom_args.size(); i++) {
|
||||
r_list.push_back(custom_args[i].replace(" ", "%20"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RunInstancesDialog::apply_custom_features(int p_instance_idx) {
|
||||
const InstanceData &instance = instances_data[p_instance_idx];
|
||||
|
||||
String raw_text;
|
||||
if (enable_multiple_instances_checkbox->is_pressed()) {
|
||||
if (instance.overrides_features()) {
|
||||
raw_text = instance.get_feature_tags();
|
||||
} else {
|
||||
raw_text = main_features_edit->get_text() + "," + instance.get_feature_tags();
|
||||
}
|
||||
} else {
|
||||
raw_text = main_features_edit->get_text();
|
||||
}
|
||||
|
||||
const Vector<String> raw_list = raw_text.split(",");
|
||||
Vector<String> stripped_features;
|
||||
|
||||
for (int i = 0; i < raw_list.size(); i++) {
|
||||
String f = raw_list[i].strip_edges();
|
||||
if (!f.is_empty()) {
|
||||
stripped_features.push_back(f);
|
||||
}
|
||||
}
|
||||
OS::get_singleton()->set_environment("GODOT_EDITOR_CUSTOM_FEATURES", String(",").join(stripped_features));
|
||||
}
|
||||
|
||||
RunInstancesDialog::RunInstancesDialog() {
|
||||
singleton = this;
|
||||
set_title(TTR("Run Instances"));
|
||||
|
||||
main_apply_timer = memnew(Timer);
|
||||
main_apply_timer->set_wait_time(0.5);
|
||||
main_apply_timer->set_one_shot(true);
|
||||
add_child(main_apply_timer);
|
||||
main_apply_timer->connect("timeout", callable_mp(this, &RunInstancesDialog::_save_main_args));
|
||||
|
||||
instance_apply_timer = memnew(Timer);
|
||||
instance_apply_timer->set_wait_time(0.5);
|
||||
instance_apply_timer->set_one_shot(true);
|
||||
add_child(instance_apply_timer);
|
||||
instance_apply_timer->connect("timeout", callable_mp(this, &RunInstancesDialog::_save_arguments));
|
||||
|
||||
VBoxContainer *main_vb = memnew(VBoxContainer);
|
||||
add_child(main_vb);
|
||||
|
||||
GridContainer *main_gc = memnew(GridContainer);
|
||||
main_gc->set_columns(2);
|
||||
main_vb->add_child(main_gc);
|
||||
|
||||
{
|
||||
Label *l = memnew(Label);
|
||||
l->set_text(TTR("Main Run Args:"));
|
||||
main_gc->add_child(l);
|
||||
}
|
||||
|
||||
{
|
||||
Label *l = memnew(Label);
|
||||
l->set_text(TTR("Main Feature Tags:"));
|
||||
main_gc->add_child(l);
|
||||
}
|
||||
|
||||
stored_data = TypedArray<Dictionary>(EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_instances_config", TypedArray<Dictionary>()));
|
||||
|
||||
main_args_edit = memnew(LineEdit);
|
||||
main_args_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
main_args_edit->set_placeholder(TTR("Space-separated arguments, example: host player1 blue"));
|
||||
main_args_edit->set_accessibility_name(TTRC("Main Run Args:"));
|
||||
main_gc->add_child(main_args_edit);
|
||||
_fetch_main_args();
|
||||
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &RunInstancesDialog::_fetch_main_args));
|
||||
main_args_edit->connect(SceneStringName(text_changed), callable_mp(this, &RunInstancesDialog::_start_main_timer).unbind(1));
|
||||
|
||||
main_features_edit = memnew(LineEdit);
|
||||
main_features_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
main_features_edit->set_placeholder(TTR("Comma-separated tags, example: demo, steam, event"));
|
||||
main_features_edit->set_text(EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_main_feature_tags", ""));
|
||||
main_features_edit->set_accessibility_name(TTRC("Main Feature Tags:"));
|
||||
main_gc->add_child(main_features_edit);
|
||||
main_features_edit->connect(SceneStringName(text_changed), callable_mp(this, &RunInstancesDialog::_start_main_timer).unbind(1));
|
||||
|
||||
main_vb->add_child(memnew(HSeparator));
|
||||
|
||||
HBoxContainer *instance_hb = memnew(HBoxContainer);
|
||||
instance_hb->set_alignment(BoxContainer::ALIGNMENT_CENTER);
|
||||
main_vb->add_child(instance_hb);
|
||||
|
||||
enable_multiple_instances_checkbox = memnew(CheckBox);
|
||||
enable_multiple_instances_checkbox->set_text(TTRC("Enable Multiple Instances"));
|
||||
enable_multiple_instances_checkbox->set_pressed(EditorSettings::get_singleton()->get_project_metadata("debug_options", "multiple_instances_enabled", false));
|
||||
instance_hb->add_child(enable_multiple_instances_checkbox);
|
||||
enable_multiple_instances_checkbox->connect(SceneStringName(pressed), callable_mp(this, &RunInstancesDialog::_start_main_timer));
|
||||
|
||||
instance_count = memnew(SpinBox);
|
||||
instance_count->set_min(1);
|
||||
instance_count->set_max(20);
|
||||
instance_count->set_value(EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_instance_count", stored_data.size()));
|
||||
instance_count->set_accessibility_name(TTRC("Number of Instances"));
|
||||
|
||||
instance_hb->add_child(instance_count);
|
||||
instance_count->connect(SceneStringName(value_changed), callable_mp(this, &RunInstancesDialog::_start_instance_timer).unbind(1));
|
||||
instance_count->connect(SceneStringName(value_changed), callable_mp(this, &RunInstancesDialog::_refresh_argument_count).unbind(1));
|
||||
enable_multiple_instances_checkbox->connect(SceneStringName(toggled), callable_mp(instance_count, &SpinBox::set_editable));
|
||||
instance_count->set_editable(enable_multiple_instances_checkbox->is_pressed());
|
||||
|
||||
instance_tree = memnew(Tree);
|
||||
instance_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
instance_tree->set_h_scroll_enabled(false);
|
||||
instance_tree->set_columns(4);
|
||||
instance_tree->set_column_titles_visible(true);
|
||||
instance_tree->set_column_title(COLUMN_OVERRIDE_ARGS, TTR("Override Main Run Args"));
|
||||
instance_tree->set_column_expand(COLUMN_OVERRIDE_ARGS, false);
|
||||
instance_tree->set_column_title(COLUMN_LAUNCH_ARGUMENTS, TTR("Launch Arguments"));
|
||||
instance_tree->set_column_title(COLUMN_OVERRIDE_FEATURES, TTR("Override Main Tags"));
|
||||
instance_tree->set_column_expand(COLUMN_OVERRIDE_FEATURES, false);
|
||||
instance_tree->set_column_title(COLUMN_FEATURE_TAGS, TTR("Feature Tags"));
|
||||
instance_tree->set_hide_root(true);
|
||||
instance_tree->set_allow_rmb_select(true);
|
||||
|
||||
popup_menu = memnew(PopupMenu);
|
||||
popup_menu->connect(SceneStringName(id_pressed), callable_mp(this, &RunInstancesDialog::_instance_menu_id_pressed));
|
||||
instance_tree->add_child(popup_menu);
|
||||
|
||||
instance_tree->connect("item_mouse_selected", callable_mp(this, &RunInstancesDialog::_instance_tree_rmb));
|
||||
instance_tree->connect("empty_clicked", callable_mp(this, &RunInstancesDialog::_instance_tree_rmb));
|
||||
main_vb->add_child(instance_tree);
|
||||
|
||||
_refresh_argument_count();
|
||||
instance_tree->connect("item_edited", callable_mp(this, &RunInstancesDialog::_start_instance_timer));
|
||||
}
|
||||
|
||||
bool RunInstancesDialog::InstanceData::overrides_run_args() const {
|
||||
return item->is_checked(COLUMN_OVERRIDE_ARGS);
|
||||
}
|
||||
|
||||
String RunInstancesDialog::InstanceData::get_launch_arguments() const {
|
||||
return item->get_text(COLUMN_LAUNCH_ARGUMENTS);
|
||||
}
|
||||
|
||||
bool RunInstancesDialog::InstanceData::overrides_features() const {
|
||||
return item->is_checked(COLUMN_OVERRIDE_FEATURES);
|
||||
}
|
||||
|
||||
String RunInstancesDialog::InstanceData::get_feature_tags() const {
|
||||
return item->get_text(COLUMN_FEATURE_TAGS);
|
||||
}
|
105
editor/run/run_instances_dialog.h
Normal file
105
editor/run/run_instances_dialog.h
Normal file
@@ -0,0 +1,105 @@
|
||||
/**************************************************************************/
|
||||
/* run_instances_dialog.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "scene/gui/dialogs.h"
|
||||
|
||||
class CheckBox;
|
||||
class LineEdit;
|
||||
class SpinBox;
|
||||
class Timer;
|
||||
class Tree;
|
||||
class TreeItem;
|
||||
class PopupMenu;
|
||||
|
||||
class RunInstancesDialog : public AcceptDialog {
|
||||
GDCLASS(RunInstancesDialog, AcceptDialog);
|
||||
|
||||
enum Columns {
|
||||
COLUMN_OVERRIDE_ARGS,
|
||||
COLUMN_LAUNCH_ARGUMENTS,
|
||||
COLUMN_OVERRIDE_FEATURES,
|
||||
COLUMN_FEATURE_TAGS,
|
||||
};
|
||||
|
||||
struct InstanceData {
|
||||
TreeItem *item = nullptr;
|
||||
|
||||
bool overrides_run_args() const;
|
||||
String get_launch_arguments() const;
|
||||
bool overrides_features() const;
|
||||
String get_feature_tags() const;
|
||||
};
|
||||
|
||||
// Right-click popup menu.
|
||||
enum {
|
||||
CLEAR_ITEM,
|
||||
CLEAR_ALL,
|
||||
};
|
||||
|
||||
inline static RunInstancesDialog *singleton = nullptr;
|
||||
|
||||
TypedArray<Dictionary> stored_data;
|
||||
Vector<InstanceData> instances_data;
|
||||
|
||||
Timer *main_apply_timer = nullptr;
|
||||
Timer *instance_apply_timer = nullptr;
|
||||
|
||||
LineEdit *main_args_edit = nullptr;
|
||||
LineEdit *main_features_edit = nullptr;
|
||||
SpinBox *instance_count = nullptr;
|
||||
CheckBox *enable_multiple_instances_checkbox = nullptr;
|
||||
Tree *instance_tree = nullptr;
|
||||
PopupMenu *popup_menu = nullptr;
|
||||
|
||||
void _fetch_main_args();
|
||||
// These 2 methods are necessary due to callable_mp() not supporting default arguments.
|
||||
void _start_main_timer();
|
||||
void _start_instance_timer();
|
||||
|
||||
void _refresh_argument_count();
|
||||
void _create_instance(InstanceData &p_instance, const Dictionary &p_data, int p_idx);
|
||||
void _save_main_args();
|
||||
void _save_arguments();
|
||||
// Separates command line arguments without splitting up quoted strings.
|
||||
Vector<String> _split_cmdline_args(const String &p_arg_string) const;
|
||||
void _instance_menu_id_pressed(int p_option);
|
||||
void _instance_tree_rmb(const Vector2 &p_pos, MouseButton p_button);
|
||||
|
||||
public:
|
||||
void popup_dialog();
|
||||
int get_instance_count() const;
|
||||
void get_argument_list_for_instance(int p_idx, List<String> &r_list) const;
|
||||
void apply_custom_features(int p_instance_idx);
|
||||
|
||||
static RunInstancesDialog *get_singleton() { return singleton; }
|
||||
RunInstancesDialog();
|
||||
};
|
Reference in New Issue
Block a user