diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index a90cf6e361ae..974fd5283b00 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1372,6 +1372,19 @@ ProjectSettings::ProjectSettings() { CRASH_COND_MSG(singleton != nullptr, "Instantiating a new ProjectSettings singleton is not supported."); singleton = this; +#ifdef TOOLS_ENABLED + // Available only at runtime in editor builds. Needs to be processed before anything else to work properly. + if (!Engine::get_singleton()->is_editor_hint()) { + String editor_features = OS::get_singleton()->get_environment("GODOT_EDITOR_CUSTOM_FEATURES"); + if (!editor_features.is_empty()) { + PackedStringArray feature_list = editor_features.split(","); + for (const String &s : feature_list) { + custom_features.insert(s); + } + } + } +#endif + GLOBAL_DEF_BASIC("application/config/name", ""); GLOBAL_DEF_BASIC(PropertyInfo(Variant::DICTIONARY, "application/config/name_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()); GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/config/description", PROPERTY_HINT_MULTILINE_TEXT), ""); diff --git a/editor/editor_run.cpp b/editor/editor_run.cpp index 33395c8712f5..d5135f419848 100644 --- a/editor/editor_run.cpp +++ b/editor/editor_run.cpp @@ -233,6 +233,7 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie) { for (int i = 0; i < instance_count; i++) { List instance_args(args); RunInstancesDialog::get_singleton()->get_argument_list_for_instance(i, instance_args); + RunInstancesDialog::get_singleton()->apply_custom_features(i); if (OS::get_singleton()->is_stdout_verbose()) { print_line(vformat("Running: %s", exec)); diff --git a/editor/plugins/debugger_editor_plugin.cpp b/editor/plugins/debugger_editor_plugin.cpp index 8f0198c2c079..3a96d3e8c365 100644 --- a/editor/plugins/debugger_editor_plugin.cpp +++ b/editor/plugins/debugger_editor_plugin.cpp @@ -96,7 +96,7 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(PopupMenu *p_debug_menu) { // Multi-instance, start/stop. debug_menu->add_separator(); - debug_menu->add_item(TTR("Run Multiple Instances..."), RUN_MULTIPLE_INSTANCES); + debug_menu->add_item(TTR("Customize Run Instances..."), RUN_MULTIPLE_INSTANCES); debug_menu->connect("id_pressed", callable_mp(this, &DebuggerEditorPlugin::_menu_option)); run_instances_dialog = memnew(RunInstancesDialog); @@ -186,7 +186,7 @@ void DebuggerEditorPlugin::_menu_option(int p_option) { } break; case RUN_MULTIPLE_INSTANCES: { - run_instances_dialog->popup_centered(); + run_instances_dialog->popup_dialog(); } break; } diff --git a/editor/run_instances_dialog.cpp b/editor/run_instances_dialog.cpp index 0004b833f295..e446af080840 100644 --- a/editor/run_instances_dialog.cpp +++ b/editor/run_instances_dialog.cpp @@ -36,11 +36,11 @@ #include "scene/gui/check_box.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" +#include "scene/gui/separator.h" #include "scene/gui/spin_box.h" +#include "scene/gui/tree.h" #include "scene/main/timer.h" -RunInstancesDialog *RunInstancesDialog::singleton = nullptr; - 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")); @@ -56,68 +56,66 @@ void RunInstancesDialog::_start_instance_timer() { } void RunInstancesDialog::_refresh_argument_count() { - while (argument_container->get_child_count() > 0) { - memdelete(argument_container->get_child(0)); + instance_tree->clear(); + instance_tree->create_item(); // Root. + + while (instance_count->get_value() > stored_data.size()) { + stored_data.append(Dictionary()); } + stored_data.resize(instance_count->get_value()); + instances_data.resize(stored_data.size()); + InstanceData *instances_write = instances_data.ptrw(); - override_list.resize(instance_count->get_value()); - argument_list.resize_zeroed(instance_count->get_value()); + for (int i = 0; i < instances_data.size(); i++) { + InstanceData instance; + const Dictionary &instance_data = stored_data[i]; - for (int i = 0; i < argument_list.size(); i++) { - VBoxContainer *instance_vb = memnew(VBoxContainer); - argument_container->add_child(instance_vb); + _create_instance(instance, instance_data, i + 1); + instances_write[i] = instance; + } +} - HBoxContainer *hbox = memnew(HBoxContainer); - instance_vb->add_child(hbox); +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; - Label *l = memnew(Label); - hbox->add_child(l); - l->set_text(vformat(TTR("Instance %d"), i + 1)); - - CheckBox *cb = memnew(CheckBox); - hbox->add_child(cb); - cb->set_text(TTR("Override Main Run Args")); - cb->set_tooltip_text(TTR("If disabled, the instance arguments will be appended after the Main Run Args.")); - cb->set_pressed(override_list[i]); - cb->set_h_size_flags(Control::SIZE_SHRINK_END | Control::SIZE_EXPAND); - cb->connect(SNAME("toggled"), callable_mp(this, &RunInstancesDialog::_start_instance_timer).unbind(1)); - instance_vb->set_meta(SNAME("override"), cb); - - LineEdit *le = memnew(LineEdit); - instance_vb->add_child(le); - le->set_text(argument_list[i]); - le->connect(SNAME("text_changed"), callable_mp(this, &RunInstancesDialog::_start_instance_timer).unbind(1)); - instance_vb->set_meta(SNAME("args"), le); - } + 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() { - override_list.clear(); - override_list.resize(argument_container->get_child_count()); - argument_list.clear(); - argument_list.resize(argument_container->get_child_count()); - - String *w = argument_list.ptrw(); - for (int i = 0; i < argument_container->get_child_count(); i++) { - const Node *instance_vb = argument_container->get_child(i); - - CheckBox *check_box = Object::cast_to(instance_vb->get_meta(SNAME("override"))); - ERR_FAIL_NULL(check_box); - override_list[i] = check_box->is_pressed(); - - LineEdit *edit = Object::cast_to(instance_vb->get_meta(SNAME("args"))); - ERR_FAIL_NULL(edit); - w[i] = edit->get_text(); + stored_data.resize(instances_data.size()); + + 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", "multiple_instances_enabled", enable_multiple_instances_checkbox->is_pressed()); - EditorSettings::get_singleton()->set_project_metadata("debug_options", "multiple_instances_overrides", override_list); - EditorSettings::get_singleton()->set_project_metadata("debug_options", "multiple_instances_arguments", argument_list); + EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_instances_config", stored_data); } Vector RunInstancesDialog::_split_cmdline_args(const String &p_arg_string) const { @@ -156,6 +154,10 @@ Vector RunInstancesDialog::_split_cmdline_args(const String &p_arg_strin return split_args; } +void RunInstancesDialog::popup_dialog() { + popup_centered(Vector2i(1200, 600) * EDSCALE); +} + int RunInstancesDialog::get_instance_count() const { if (enable_multiple_instances_checkbox->is_pressed()) { return instance_count->get_value(); @@ -165,12 +167,16 @@ int RunInstancesDialog::get_instance_count() const { } void RunInstancesDialog::get_argument_list_for_instance(int p_idx, List &r_list) const { - bool override_args = override_list[p_idx]; + 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 && override_args) { - raw_custom_args = argument_list[p_idx]; + 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(); } @@ -218,15 +224,37 @@ void RunInstancesDialog::get_argument_list_for_instance(int p_idx, List } } } +} + +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 raw_list = raw_text.split(","); + Vector stripped_features; - if (use_multiple_instances && !override_args) { - r_list.push_back(argument_list[p_idx]); + 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_min_size(Vector2i(0, 600 * EDSCALE)); + set_title(TTR("Run Instances")); main_apply_timer = memnew(Timer); main_apply_timer->set_wait_time(0.5); @@ -255,45 +283,78 @@ RunInstancesDialog::RunInstancesDialog() { ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &RunInstancesDialog::_fetch_main_args)); main_args_edit->connect("text_changed", callable_mp(this, &RunInstancesDialog::_start_main_timer).unbind(1)); + { + Label *l = memnew(Label); + main_vb->add_child(l); + l->set_text(TTR("Main Feature Tags:")); + } + + main_features_edit = memnew(LineEdit); + main_features_edit->set_text(EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_main_feature_tags", "")); + main_vb->add_child(main_features_edit); + main_features_edit->connect("text_changed", callable_mp(this, &RunInstancesDialog::_start_main_timer).unbind(1)); + + { + HSeparator *sep = memnew(HSeparator); + main_vb->add_child(sep); + } + enable_multiple_instances_checkbox = memnew(CheckBox); enable_multiple_instances_checkbox->set_text(TTR("Enable Multiple Instances")); enable_multiple_instances_checkbox->set_pressed(EditorSettings::get_singleton()->get_project_metadata("debug_options", "multiple_instances_enabled", false)); main_vb->add_child(enable_multiple_instances_checkbox); - enable_multiple_instances_checkbox->connect("pressed", callable_mp(this, &RunInstancesDialog::_start_instance_timer)); + enable_multiple_instances_checkbox->connect("pressed", callable_mp(this, &RunInstancesDialog::_start_main_timer)); - override_list = EditorSettings::get_singleton()->get_project_metadata("debug_options", "multiple_instances_overrides", varray(false, false, false, false)); - argument_list = EditorSettings::get_singleton()->get_project_metadata("debug_options", "multiple_instances_arguments", PackedStringArray{ "", "", "", "" }); + stored_data = TypedArray(EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_instances_config", TypedArray())); instance_count = memnew(SpinBox); instance_count->set_min(1); instance_count->set_max(20); - instance_count->set_value(argument_list.size()); + instance_count->set_value(stored_data.size()); main_vb->add_child(instance_count); instance_count->connect("value_changed", callable_mp(this, &RunInstancesDialog::_start_instance_timer).unbind(1)); instance_count->connect("value_changed", callable_mp(this, &RunInstancesDialog::_refresh_argument_count).unbind(1)); enable_multiple_instances_checkbox->connect("toggled", callable_mp(instance_count, &SpinBox::set_editable)); + instance_count->set_editable(enable_multiple_instances_checkbox->is_pressed()); { Label *l = memnew(Label); - l->set_text(TTR("Launch Arguments")); + l->set_text(TTR("Instance Configuration")); l->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + l->set_theme_type_variation("HeaderSmall"); main_vb->add_child(l); } - { - ScrollContainer *arguments_scroll = memnew(ScrollContainer); - arguments_scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); - arguments_scroll->set_h_size_flags(Control::SIZE_EXPAND_FILL); - arguments_scroll->set_v_size_flags(Control::SIZE_EXPAND_FILL); - main_vb->add_child(arguments_scroll); - - argument_container = memnew(VBoxContainer); - argument_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); - arguments_scroll->add_child(argument_container); - } + 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); + 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); +} - set_title(TTR("Run Multiple Instances")); - set_min_size(Size2i(400 * EDSCALE, 0)); +String RunInstancesDialog::InstanceData::get_feature_tags() const { + return item->get_text(COLUMN_FEATURE_TAGS); } diff --git a/editor/run_instances_dialog.h b/editor/run_instances_dialog.h index cc4cd6eb5bd1..8e0007dc8e7b 100644 --- a/editor/run_instances_dialog.h +++ b/editor/run_instances_dialog.h @@ -37,24 +37,41 @@ class CheckBox; class LineEdit; class SpinBox; class Timer; -class VBoxContainer; +class Tree; +class TreeItem; class RunInstancesDialog : public AcceptDialog { GDCLASS(RunInstancesDialog, AcceptDialog); -private: - static RunInstancesDialog *singleton; + 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; + }; + + inline static RunInstancesDialog *singleton = nullptr; + + TypedArray stored_data; + Vector 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; - VBoxContainer *argument_container = nullptr; - - Array override_list; - PackedStringArray argument_list; + Tree *instance_tree = nullptr; void _fetch_main_args(); // These 2 methods are necessary due to callable_mp() not supporting default arguments. @@ -62,14 +79,17 @@ class RunInstancesDialog : public AcceptDialog { 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 _split_cmdline_args(const String &p_arg_string) const; public: + void popup_dialog(); int get_instance_count() const; void get_argument_list_for_instance(int p_idx, List &r_list) const; + void apply_custom_features(int p_instance_idx); static RunInstancesDialog *get_singleton() { return singleton; } RunInstancesDialog();