diff options
Diffstat (limited to 'editor')
41 files changed, 1744 insertions, 268 deletions
diff --git a/editor/action_map_editor.cpp b/editor/action_map_editor.cpp index cb8d98932d..ae54c20fe2 100644 --- a/editor/action_map_editor.cpp +++ b/editor/action_map_editor.cpp @@ -449,10 +449,14 @@ void ActionMapEditor::update_action_list(const Vector<ActionInfo> &p_action_info // First Column - Icon Ref<InputEventKey> k = event; if (k.is_valid()) { - if (k->get_physical_keycode() == Key::NONE) { + if (k->get_physical_keycode() == Key::NONE && k->get_keycode() == Key::NONE && k->get_key_label() != Key::NONE) { + event_item->set_icon(0, action_tree->get_theme_icon(SNAME("KeyboardLabel"), SNAME("EditorIcons"))); + } else if (k->get_keycode() != Key::NONE) { event_item->set_icon(0, action_tree->get_theme_icon(SNAME("Keyboard"), SNAME("EditorIcons"))); - } else { + } else if (k->get_physical_keycode() != Key::NONE) { event_item->set_icon(0, action_tree->get_theme_icon(SNAME("KeyboardPhysical"), SNAME("EditorIcons"))); + } else { + event_item->set_icon(0, action_tree->get_theme_icon(SNAME("KeyboardError"), SNAME("EditorIcons"))); } } diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index 9ff480f130..db12dbc72b 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -30,6 +30,7 @@ #include "connections_dialog.h" +#include "core/config/project_settings.h" #include "editor/doc_tools.h" #include "editor/editor_help.h" #include "editor/editor_node.h" @@ -165,6 +166,7 @@ void ConnectDialog::_tree_node_selected() { if (!edit_mode) { set_dst_method(generate_method_callback_name(source, signal, current)); } + _update_method_tree(); _update_ok_enabled(); } @@ -182,6 +184,11 @@ void ConnectDialog::_unbind_count_changed(double p_count) { } } +void ConnectDialog::_method_selected() { + TreeItem *selected_item = method_tree->get_selected(); + dst_method->set_text(selected_item->get_text(0)); +} + /* * Adds a new parameter bind to connection. */ @@ -242,14 +249,150 @@ StringName ConnectDialog::generate_method_callback_name(Node *p_source, String p String dst_method; if (p_source == p_target) { - dst_method = String(EDITOR_GET("interface/editors/default_signal_callback_to_self_name")).format(subst); + dst_method = String(GLOBAL_GET("editor/naming/default_signal_callback_to_self_name")).format(subst); } else { - dst_method = String(EDITOR_GET("interface/editors/default_signal_callback_name")).format(subst); + dst_method = String(GLOBAL_GET("editor/naming/default_signal_callback_name")).format(subst); } return dst_method; } +void ConnectDialog::_create_method_tree_items(const List<MethodInfo> &p_methods, TreeItem *p_parent_item) { + for (const MethodInfo &mi : p_methods) { + TreeItem *method_item = method_tree->create_item(p_parent_item); + method_item->set_text(0, mi.name); + if (mi.return_val.type == Variant::NIL) { + method_item->set_icon(0, get_theme_icon(SNAME("Variant"), "EditorIcons")); + } else { + method_item->set_icon(0, get_theme_icon(Variant::get_type_name(mi.return_val.type), "EditorIcons")); + } + } +} + +List<MethodInfo> ConnectDialog::_filter_method_list(const List<MethodInfo> &p_methods, const MethodInfo &p_signal, const String &p_search_string) const { + bool check_signal = compatible_methods_only->is_pressed(); + List<MethodInfo> ret; + + for (const MethodInfo &mi : p_methods) { + if (!p_search_string.is_empty() && !mi.name.contains(p_search_string)) { + continue; + } + + if (check_signal) { + if (mi.arguments.size() != p_signal.arguments.size()) { + continue; + } + + bool type_mismatch = false; + const List<PropertyInfo>::Element *E = p_signal.arguments.front(); + for (const List<PropertyInfo>::Element *F = mi.arguments.front(); F; F = F->next(), E = E->next()) { + Variant::Type stype = E->get().type; + Variant::Type mtype = F->get().type; + + if (stype != Variant::NIL && mtype != Variant::NIL && stype != mtype) { + type_mismatch = true; + break; + } + } + + if (type_mismatch) { + continue; + } + } + ret.push_back(mi); + } + return ret; +} + +void ConnectDialog::_update_method_tree() { + method_tree->clear(); + + Color disabled_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")) * 0.7; + String search_string = method_search->get_text(); + Node *target = tree->get_selected(); + if (!target) { + return; + } + + MethodInfo signal_info; + if (compatible_methods_only->is_pressed()) { + List<MethodInfo> signals; + source->get_signal_list(&signals); + for (const MethodInfo &mi : signals) { + if (mi.name == signal) { + signal_info = mi; + break; + } + } + } + + TreeItem *root_item = method_tree->create_item(); + root_item->set_text(0, TTR("Methods")); + root_item->set_selectable(0, false); + + // If a script is attached, get methods from it. + ScriptInstance *si = target->get_script_instance(); + if (si) { + TreeItem *si_item = method_tree->create_item(root_item); + si_item->set_text(0, TTR("Attached Script")); + si_item->set_icon(0, get_theme_icon(SNAME("Script"), SNAME("EditorIcons"))); + si_item->set_selectable(0, false); + + List<MethodInfo> methods; + si->get_method_list(&methods); + methods = _filter_method_list(methods, signal_info, search_string); + + if (methods.is_empty()) { + si_item->set_custom_color(0, disabled_color); + } else { + _create_method_tree_items(methods, si_item); + } + } + + if (script_methods_only->is_pressed()) { + return; + } + + // Get methods from each class in the heirarchy. + StringName current_class = target->get_class_name(); + do { + TreeItem *class_item = method_tree->create_item(root_item); + class_item->set_text(0, current_class); + Ref<Texture2D> icon = get_theme_icon(SNAME("Node"), SNAME("EditorIcons")); + if (has_theme_icon(current_class, SNAME("EditorIcons"))) { + icon = get_theme_icon(current_class, SNAME("EditorIcons")); + } + class_item->set_icon(0, icon); + class_item->set_selectable(0, false); + + List<MethodInfo> methods; + ClassDB::get_method_list(current_class, &methods, true); + methods = _filter_method_list(methods, signal_info, search_string); + + if (methods.is_empty()) { + class_item->set_custom_color(0, disabled_color); + } else { + _create_method_tree_items(methods, class_item); + } + current_class = ClassDB::get_parent_class_nocheck(current_class); + } while (current_class != StringName()); +} + +void ConnectDialog::_method_check_button_pressed(const CheckButton *p_button) { + if (p_button == script_methods_only) { + EditorSettings::get_singleton()->set_project_metadata("editor_metadata", "show_script_methods_only", p_button->is_pressed()); + } else if (p_button == compatible_methods_only) { + EditorSettings::get_singleton()->set_project_metadata("editor_metadata", "show_compatible_methods_only", p_button->is_pressed()); + } + _update_method_tree(); +} + +void ConnectDialog::_open_method_popup() { + method_popup->popup_centered(); + method_search->clear(); + method_search->grab_focus(); +} + /* * Enables or disables the connect button. The connect button is enabled if a * node is selected and valid in the selected mode. @@ -262,7 +405,7 @@ void ConnectDialog::_update_ok_enabled() { return; } - if (!advanced->is_pressed() && target->get_script().is_null()) { + if (dst_method->get_text().is_empty()) { get_ok_button()->set_disabled(true); return; } @@ -288,14 +431,12 @@ void ConnectDialog::_notification(int p_what) { style->set_content_margin(SIDE_TOP, style->get_content_margin(SIDE_TOP) + 1.0); from_signal->add_theme_style_override("normal", style); } + method_search->set_right_icon(get_theme_icon("Search", "EditorIcons")); } break; } } void ConnectDialog::_bind_methods() { - ClassDB::bind_method("_cancel", &ConnectDialog::_cancel_pressed); - ClassDB::bind_method("_update_ok_enabled", &ConnectDialog::_update_ok_enabled); - ADD_SIGNAL(MethodInfo("connected")); } @@ -437,7 +578,6 @@ void ConnectDialog::_advanced_pressed() { error_label->set_visible(!_find_first_script(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root())); } - _update_ok_enabled(); EditorSettings::get_singleton()->set_project_metadata("editor_metadata", "use_advanced_connections", advanced->is_pressed()); popup_centered(); @@ -457,9 +597,18 @@ ConnectDialog::ConnectDialog() { main_hb->add_child(vbc_left); vbc_left->set_h_size_flags(Control::SIZE_EXPAND_FILL); + HBoxContainer *from_signal_hb = memnew(HBoxContainer); + from_signal = memnew(LineEdit); from_signal->set_editable(false); - vbc_left->add_margin_child(TTR("From Signal:"), from_signal); + from_signal->set_h_size_flags(Control::SIZE_EXPAND_FILL); + from_signal_hb->add_child(from_signal); + + advanced = memnew(CheckButton(TTR("Advanced"))); + from_signal_hb->add_child(advanced); + advanced->connect("pressed", callable_mp(this, &ConnectDialog::_advanced_pressed)); + + vbc_left->add_margin_child(TTR("From Signal:"), from_signal_hb); tree = memnew(SceneTreeEditor(false)); tree->set_connecting_signal(true); @@ -476,6 +625,39 @@ ConnectDialog::ConnectDialog() { vbc_left->add_child(error_label); error_label->hide(); + method_popup = memnew(AcceptDialog); + method_popup->set_title(TTR("Select Method")); + method_popup->set_min_size(Vector2(400, 600) * EDSCALE); + add_child(method_popup); + + VBoxContainer *method_vbc = memnew(VBoxContainer); + method_popup->add_child(method_vbc); + + method_search = memnew(LineEdit); + method_vbc->add_child(method_search); + method_search->set_placeholder(TTR("Filter Methods")); + method_search->set_clear_button_enabled(true); + method_search->connect("text_changed", callable_mp(this, &ConnectDialog::_update_method_tree).unbind(1)); + + method_tree = memnew(Tree); + method_vbc->add_child(method_tree); + method_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); + method_tree->set_hide_root(true); + method_tree->connect("item_selected", callable_mp(this, &ConnectDialog::_method_selected)); + method_tree->connect("item_activated", callable_mp((Window *)method_popup, &Window::hide)); + + script_methods_only = memnew(CheckButton(TTR("Script Methods Only"))); + method_vbc->add_child(script_methods_only); + script_methods_only->set_h_size_flags(Control::SIZE_SHRINK_END); + script_methods_only->set_pressed(EditorSettings::get_singleton()->get_project_metadata("editor_metadata", "show_script_methods_only", true)); + script_methods_only->connect("pressed", callable_mp(this, &ConnectDialog::_method_check_button_pressed).bind(script_methods_only)); + + compatible_methods_only = memnew(CheckButton(TTR("Compatible Methods Only"))); + method_vbc->add_child(compatible_methods_only); + compatible_methods_only->set_h_size_flags(Control::SIZE_SHRINK_END); + compatible_methods_only->set_pressed(EditorSettings::get_singleton()->get_project_metadata("editor_metadata", "show_compatible_methods_only", true)); + compatible_methods_only->connect("pressed", callable_mp(this, &ConnectDialog::_method_check_button_pressed).bind(compatible_methods_only)); + vbc_right = memnew(VBoxContainer); main_hb->add_child(vbc_right); vbc_right->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -521,10 +703,20 @@ ConnectDialog::ConnectDialog() { vbc_right->add_margin_child(TTR("Unbind Signal Arguments:"), unbind_count); + HBoxContainer *hbc_method = memnew(HBoxContainer); + vbc_left->add_margin_child(TTR("Receiver Method:"), hbc_method); + dst_method = memnew(LineEdit); dst_method->set_h_size_flags(Control::SIZE_EXPAND_FILL); + dst_method->connect("text_changed", callable_mp(method_tree, &Tree::deselect_all).unbind(1)); dst_method->connect("text_submitted", callable_mp(this, &ConnectDialog::_text_submitted)); - vbc_left->add_margin_child(TTR("Receiver Method:"), dst_method); + hbc_method->add_child(dst_method); + + Button *open_tree_button = memnew(Button); + open_tree_button->set_flat(false); + open_tree_button->set_text("..."); + open_tree_button->connect("pressed", callable_mp(this, &ConnectDialog::_open_method_popup)); + hbc_method->add_child(open_tree_button); advanced = memnew(CheckButton); vbc_left->add_child(advanced); @@ -566,7 +758,7 @@ ConnectDialog::~ConnectDialog() { // Originally copied and adapted from EditorProperty, try to keep style in sync. Control *ConnectionsDockTree::make_custom_tooltip(const String &p_text) const { EditorHelpBit *help_bit = memnew(EditorHelpBit); - help_bit->get_rich_text()->set_fixed_size_to_width(360 * EDSCALE); + help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1)); // p_text is expected to be something like this: // "gui_input::(event: InputEvent)::<Signal description>" @@ -1238,9 +1430,6 @@ ConnectionsDock::ConnectionsDock() { tree->connect("item_mouse_selected", callable_mp(this, &ConnectionsDock::_rmb_pressed)); add_theme_constant_override("separation", 3 * EDSCALE); - - EDITOR_DEF("interface/editors/default_signal_callback_name", "_on_{node_name}_{signal_name}"); - EDITOR_DEF("interface/editors/default_signal_callback_to_self_name", "_on_{signal_name}"); } ConnectionsDock::~ConnectionsDock() { diff --git a/editor/connections_dialog.h b/editor/connections_dialog.h index 829a98caed..0bea897976 100644 --- a/editor/connections_dialog.h +++ b/editor/connections_dialog.h @@ -114,9 +114,15 @@ private: bool first_popup = true; NodePath dst_path; VBoxContainer *vbc_right = nullptr; - SceneTreeEditor *tree = nullptr; AcceptDialog *error = nullptr; + + AcceptDialog *method_popup = nullptr; + Tree *method_tree = nullptr; + LineEdit *method_search = nullptr; + CheckButton *script_methods_only = nullptr; + CheckButton *compatible_methods_only = nullptr; + SpinBox *unbind_count = nullptr; EditorInspector *bind_editor = nullptr; OptionButton *type_list = nullptr; @@ -132,6 +138,14 @@ private: void _item_activated(); void _text_submitted(const String &p_text); void _tree_node_selected(); + + void _method_selected(); + void _create_method_tree_items(const List<MethodInfo> &p_methods, TreeItem *p_parent_item); + List<MethodInfo> _filter_method_list(const List<MethodInfo> &p_methods, const MethodInfo &p_signal, const String &p_search_string) const; + void _update_method_tree(); + void _method_check_button_pressed(const CheckButton *p_button); + void _open_method_popup(); + void _unbind_count_changed(double p_count); void _add_bind(); void _remove_bind(); diff --git a/editor/dependency_editor.cpp b/editor/dependency_editor.cpp index a925e2d1d3..c98ec7b2d5 100644 --- a/editor/dependency_editor.cpp +++ b/editor/dependency_editor.cpp @@ -536,12 +536,17 @@ void DependencyRemoveDialog::show(const Vector<String> &p_folders, const Vector< } void DependencyRemoveDialog::ok_pressed() { - for (int i = 0; i < files_to_delete.size(); ++i) { - if (ResourceCache::has(files_to_delete[i])) { - Ref<Resource> res = ResourceCache::get_ref(files_to_delete[i]); + for (const KeyValue<String, String> &E : all_remove_files) { + String file = E.key; + + if (ResourceCache::has(file)) { + Ref<Resource> res = ResourceCache::get_ref(file); + emit_signal(SNAME("resource_removed"), res); res->set_path(""); } + } + for (int i = 0; i < files_to_delete.size(); ++i) { // If the file we are deleting for e.g. the main scene, default environment, // or audio bus layout, we must clear its definition in Project Settings. if (files_to_delete[i] == String(GLOBAL_GET("application/config/icon"))) { @@ -621,6 +626,7 @@ void DependencyRemoveDialog::ok_pressed() { } void DependencyRemoveDialog::_bind_methods() { + ADD_SIGNAL(MethodInfo("resource_removed", PropertyInfo(Variant::OBJECT, "obj"))); ADD_SIGNAL(MethodInfo("file_removed", PropertyInfo(Variant::STRING, "file"))); ADD_SIGNAL(MethodInfo("folder_removed", PropertyInfo(Variant::STRING, "folder"))); } diff --git a/editor/editor_data.h b/editor/editor_data.h index 6a89b3572c..d00501280d 100644 --- a/editor/editor_data.h +++ b/editor/editor_data.h @@ -201,7 +201,6 @@ public: String get_scene_type(int p_idx) const; void set_scene_path(int p_idx, const String &p_path); Ref<Script> get_scene_root_script(int p_idx) const; - void set_edited_scene_version(uint64_t version, int p_scene_idx = -1); void set_scene_modified_time(int p_idx, uint64_t p_time); uint64_t get_scene_modified_time(int p_idx) const; void clear_edited_scenes(); diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 4cf947b006..e11251596a 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -2370,7 +2370,7 @@ EditorHelpBit::EditorHelpBit() { rich_text = memnew(RichTextLabel); add_child(rich_text); rich_text->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked)); - rich_text->set_fit_content_height(true); + rich_text->set_fit_content(true); set_custom_minimum_size(Size2(0, 50 * EDSCALE)); } diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 4753761f05..0166d4c719 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -886,7 +886,7 @@ void EditorProperty::_update_pin_flags() { static Control *make_help_bit(const String &p_text, bool p_property) { EditorHelpBit *help_bit = memnew(EditorHelpBit); - help_bit->get_rich_text()->set_fixed_size_to_width(360 * EDSCALE); + help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1)); PackedStringArray slices = p_text.split("::", false); if (slices.is_empty()) { diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 19d991b112..173cbc6893 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -58,6 +58,7 @@ #include "scene/gui/tab_bar.h" #include "scene/gui/tab_container.h" #include "scene/main/window.h" +#include "scene/property_utils.h" #include "scene/resources/packed_scene.h" #include "servers/display_server.h" #include "servers/navigation_server_3d.h" @@ -124,6 +125,7 @@ #include "editor/plugins/asset_library_editor_plugin.h" #include "editor/plugins/canvas_item_editor_plugin.h" #include "editor/plugins/debugger_editor_plugin.h" +#include "editor/plugins/dedicated_server_export_plugin.h" #include "editor/plugins/editor_preview_plugins.h" #include "editor/plugins/editor_resource_conversion_plugin.h" #include "editor/plugins/gdextension_export_plugin.h" @@ -997,6 +999,7 @@ void EditorNode::_resources_reimported(const Vector<String> &p_resources) { for (const String &E : scenes) { reload_scene(E); + reload_instances_with_path_in_edited_scenes(E); } scene_tabs->set_current_tab(current_tab); @@ -2112,6 +2115,7 @@ void EditorNode::edit_item(Object *p_object, Object *p_editing_owner) { } } active_plugins[owner_id].insert(plugin); + editor_plugins_over->add_plugin(plugin); plugin->edit(p_object); plugin->make_visible(true); } @@ -3085,7 +3089,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } String EditorNode::adjust_scene_name_casing(const String &root_name) { - switch (GLOBAL_GET("editor/scene/scene_naming").operator int()) { + switch (GLOBAL_GET("editor/naming/scene_name_casing").operator int()) { case SCENE_NAME_CASING_AUTO: // Use casing of the root node. break; @@ -3932,6 +3936,134 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b return OK; } +HashMap<StringName, Variant> EditorNode::get_modified_properties_for_node(Node *p_node) { + HashMap<StringName, Variant> modified_property_map; + + List<PropertyInfo> pinfo; + p_node->get_property_list(&pinfo); + for (const PropertyInfo &E : pinfo) { + if (E.usage & PROPERTY_USAGE_STORAGE) { + bool is_valid_revert = false; + Variant revert_value = EditorPropertyRevert::get_property_revert_value(p_node, E.name, &is_valid_revert); + Variant current_value = p_node->get(E.name); + if (is_valid_revert) { + if (PropertyUtils::is_property_value_different(current_value, revert_value)) { + modified_property_map[E.name] = current_value; + } + } + } + } + + return modified_property_map; +} + +void EditorNode::update_diff_data_for_node( + Node *p_edited_scene, + Node *p_root, + Node *p_node, + HashMap<NodePath, ModificationNodeEntry> &p_modification_table, + List<AdditiveNodeEntry> &p_addition_list) { + bool node_part_of_subscene = p_node != p_edited_scene && + p_edited_scene->get_scene_inherited_state().is_valid() && + p_edited_scene->get_scene_inherited_state()->find_node_by_path(p_edited_scene->get_path_to(p_node)) >= 0; + + // Loop through the owners until either we reach the root node or nullptr + Node *valid_node_owner = p_node->get_owner(); + while (valid_node_owner) { + if (valid_node_owner == p_root) { + break; + } + valid_node_owner = valid_node_owner->get_owner(); + } + + if ((valid_node_owner == p_root && (p_root != p_edited_scene || !p_edited_scene->get_scene_file_path().is_empty())) || node_part_of_subscene || p_node == p_root) { + HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node); + + // Find all valid connections to other nodes. + List<Connection> connections_to; + p_node->get_all_signal_connections(&connections_to); + + List<ConnectionWithNodePath> valid_connections_to; + for (const Connection &c : connections_to) { + Node *connection_target_node = Object::cast_to<Node>(c.callable.get_object()); + if (connection_target_node) { + // TODO: add support for reinstating custom callables + if (!c.callable.is_custom()) { + ConnectionWithNodePath connection_to; + connection_to.connection = c; + connection_to.node_path = p_node->get_path_to(connection_target_node); + valid_connections_to.push_back(connection_to); + } + } + } + + // Find all valid connections from other nodes. + List<Connection> connections_from; + p_node->get_signals_connected_to_this(&connections_from); + + List<Connection> valid_connections_from; + for (const Connection &c : connections_from) { + Node *source_node = Object::cast_to<Node>(c.signal.get_object()); + + Node *valid_source_owner = nullptr; + if (source_node) { + valid_source_owner = source_node->get_owner(); + while (valid_source_owner) { + if (valid_source_owner == p_root) { + break; + } + valid_source_owner = valid_source_owner->get_owner(); + } + } + + if (!source_node || valid_source_owner == nullptr) { + // TODO: add support for reinstating custom callables + if (!c.callable.is_custom()) { + valid_connections_from.push_back(c); + } + } + } + + // Find all node groups. + List<Node::GroupInfo> groups; + p_node->get_groups(&groups); + + if (!modified_properties.is_empty() || !valid_connections_to.is_empty() || !valid_connections_from.is_empty() || !groups.is_empty()) { + ModificationNodeEntry modification_node_entry; + modification_node_entry.property_table = modified_properties; + modification_node_entry.connections_to = valid_connections_to; + modification_node_entry.connections_from = valid_connections_from; + modification_node_entry.groups = groups; + + p_modification_table[p_root->get_path_to(p_node)] = modification_node_entry; + } + } else { + AdditiveNodeEntry new_additive_node_entry; + new_additive_node_entry.node = p_node; + new_additive_node_entry.parent = p_root->get_path_to(p_node->get_parent()); + new_additive_node_entry.owner = p_node->get_owner(); + new_additive_node_entry.index = p_node->get_index(); + + Node2D *node_2d = Object::cast_to<Node2D>(p_node); + if (node_2d) { + new_additive_node_entry.transform_2d = node_2d->get_relative_transform_to_parent(node_2d->get_parent()); + } + Node3D *node_3d = Object::cast_to<Node3D>(p_node); + if (node_3d) { + new_additive_node_entry.transform_3d = node_3d->get_relative_transform(node_3d->get_parent()); + } + p_addition_list.push_back(new_additive_node_entry); + + return; + } + + for (int i = 0; i < p_node->get_child_count(); i++) { + Node *child = p_node->get_child(i); + update_diff_data_for_node(p_edited_scene, p_root, child, p_modification_table, p_addition_list); + } +} +// + void EditorNode::open_request(const String &p_path) { if (!opening_prev) { List<String>::Element *prev_scene_item = previous_scenes.find(p_path); @@ -5309,7 +5441,7 @@ void EditorNode::_scene_tab_input(const Ref<InputEvent> &p_input) { _scene_tab_closed(scene_tabs->get_hovered_tab()); } } else { - if ((mb->get_button_index() == MouseButton::LEFT && mb->is_double_click()) || (mb->get_button_index() == MouseButton::MIDDLE && mb->is_pressed())) { + if (mb->get_button_index() == MouseButton::LEFT && mb->is_double_click()) { _menu_option_confirm(FILE_NEW_SCENE, true); } } @@ -5788,6 +5920,353 @@ void EditorNode::reload_scene(const String &p_path) { scene_tabs->set_current_tab(current_tab); } +void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, List<Node *> &p_instance_list) { + String scene_file_path = p_node->get_scene_file_path(); + + // This is going to get messy... + if (p_node->get_scene_file_path() == p_instance_path) { + p_instance_list.push_back(p_node); + } else { + Node *current_node = p_node; + + Ref<SceneState> inherited_state = current_node->get_scene_inherited_state(); + while (inherited_state.is_valid()) { + String inherited_path = inherited_state->get_path(); + if (inherited_path == p_instance_path) { + p_instance_list.push_back(p_node); + break; + } + + inherited_state = inherited_state->get_base_scene_state(); + } + } + + for (int i = 0; i < p_node->get_child_count(); i++) { + Node *child = p_node->get_child(i); + find_all_instances_inheriting_path_in_node(p_root, child, p_instance_path, p_instance_list); + } +} + +void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_instance_path) { + int original_edited_scene_idx = editor_data.get_edited_scene(); + HashMap<int, List<Node *>> edited_scene_map; + + // Walk through each opened scene to get a global list of all instances which match + // the current reimported scenes. + for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { + if (editor_data.get_scene_path(i) != p_instance_path) { + Node *edited_scene_root = editor_data.get_edited_scene_root(i); + + if (edited_scene_root) { + List<Node *> valid_nodes; + find_all_instances_inheriting_path_in_node(edited_scene_root, edited_scene_root, p_instance_path, valid_nodes); + if (valid_nodes.size() > 0) { + edited_scene_map[i] = valid_nodes; + } + } + } + } + + if (edited_scene_map.size() > 0) { + // Reload the new instance. + Error err; + Ref<PackedScene> instance_scene_packed_scene = ResourceLoader::load(p_instance_path, "", ResourceFormatLoader::CACHE_MODE_IGNORE, &err); + instance_scene_packed_scene->set_path(p_instance_path, true); + + ERR_FAIL_COND(err != OK); + ERR_FAIL_COND(instance_scene_packed_scene.is_null()); + + HashMap<String, Ref<PackedScene>> local_scene_cache; + local_scene_cache[p_instance_path] = instance_scene_packed_scene; + + for (const KeyValue<int, List<Node *>> &edited_scene_map_elem : edited_scene_map) { + // Set the current scene. + int current_scene_idx = edited_scene_map_elem.key; + editor_data.set_edited_scene(current_scene_idx); + Node *current_edited_scene = editor_data.get_edited_scene_root(current_scene_idx); + + // Clear the history for this tab (should we allow history to be retained?). + EditorUndoRedoManager::get_singleton()->clear_history(); + + // Update the version + editor_data.is_scene_changed(current_scene_idx); + + for (Node *original_node : edited_scene_map_elem.value) { + // Walk the tree for the current node and extract relevant diff data, storing it in the modification table. + // For additional nodes which are part of the current scene, they get added to the addition table. + HashMap<NodePath, ModificationNodeEntry> modification_table; + List<AdditiveNodeEntry> addition_list; + update_diff_data_for_node(current_edited_scene, original_node, original_node, modification_table, addition_list); + + // Disconnect all relevant connections, all connections from and persistent connections to. + for (const KeyValue<NodePath, ModificationNodeEntry> &modification_table_entry : modification_table) { + for (Connection conn : modification_table_entry.value.connections_from) { + conn.signal.get_object()->disconnect(conn.signal.get_name(), conn.callable); + } + for (ConnectionWithNodePath cwnp : modification_table_entry.value.connections_to) { + Connection conn = cwnp.connection; + if (conn.flags & CONNECT_PERSIST) { + conn.signal.get_object()->disconnect(conn.signal.get_name(), conn.callable); + } + } + } + + // Store all the paths for any selected nodes which are ancestors of the node we're replacing. + List<NodePath> selected_node_paths; + for (Node *selected_node : editor_selection->get_selected_node_list()) { + if (selected_node == original_node || original_node->is_ancestor_of(selected_node)) { + selected_node_paths.push_back(original_node->get_path_to(selected_node)); + editor_selection->remove_node(selected_node); + } + } + + // Remove all nodes which were added as additional elements (they will be restored later). + for (AdditiveNodeEntry additive_node_entry : addition_list) { + Node *addition_node = additive_node_entry.node; + addition_node->get_parent()->remove_child(addition_node); + } + + // Clear ownership of the nodes (kind of hack to workaround an issue with + // replace_by when called on nodes in other tabs). + List<Node *> nodes_owned_by_original_node; + original_node->get_owned_by(original_node, &nodes_owned_by_original_node); + for (Node *owned_node : nodes_owned_by_original_node) { + owned_node->set_owner(nullptr); + } + + // Delete all the remaining node children. + while (original_node->get_child_count()) { + Node *child = original_node->get_child(0); + + original_node->remove_child(child); + child->queue_free(); + } + + // Reset the editable instance state. + bool is_editable = true; + Node *owner = original_node->get_owner(); + if (owner) { + is_editable = owner->is_editable_instance(original_node); + } + + // Load a replacement scene for the node. + Ref<PackedScene> current_packed_scene; + if (original_node->get_scene_file_path() == p_instance_path) { + // If the node file name directly matches the scene we're replacing, + // just load it since we already cached it. + current_packed_scene = instance_scene_packed_scene; + } else { + // Otherwise, check the inheritance chain, reloading and caching any scenes + // we require along the way. + List<String> required_load_paths; + String scene_path = original_node->get_scene_file_path(); + // Do we need to check if the paths are empty? + if (!scene_path.is_empty()) { + required_load_paths.push_front(scene_path); + } + Ref<SceneState> inherited_state = original_node->get_scene_inherited_state(); + while (inherited_state.is_valid()) { + String inherited_path = inherited_state->get_path(); + // Do we need to check if the paths are empty? + if (!inherited_path.is_empty()) { + required_load_paths.push_front(inherited_path); + } + inherited_state = inherited_state->get_base_scene_state(); + } + + // Ensure the inheritance chain is loaded in the correct order so that cache can + // be properly updated. + for (String path : required_load_paths) { + if (!local_scene_cache.find(path)) { + current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_IGNORE, &err); + current_packed_scene->set_path(path, true); + local_scene_cache[path] = current_packed_scene; + } else { + current_packed_scene = local_scene_cache[path]; + } + } + } + + ERR_FAIL_COND(current_packed_scene.is_null()); + + // Instantiate the node. + Node *instantiated_node = nullptr; + if (current_packed_scene.is_valid()) { + instantiated_node = current_packed_scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); + } + + ERR_FAIL_COND(!instantiated_node); + + bool original_node_is_displayed_folded = original_node->is_displayed_folded(); + bool original_node_scene_instance_load_placeholder = original_node->get_scene_instance_load_placeholder(); + + // Update the name to match + instantiated_node->set_name(original_node->get_name()); + + // Is this replacing the edited root node? + String original_node_file_path = original_node->get_scene_file_path(); + + if (current_edited_scene == original_node) { + instantiated_node->set_scene_instance_state(original_node->get_scene_instance_state()); + // Fix unsaved inherited scene + if (original_node_file_path.is_empty()) { + Ref<SceneState> state = current_packed_scene->get_state(); + state->set_path(current_packed_scene->get_path()); + instantiated_node->set_scene_inherited_state(state); + instantiated_node->set_scene_file_path(String()); + } + editor_data.set_edited_scene_root(instantiated_node); + current_edited_scene = instantiated_node; + + if (original_node->is_inside_tree()) { + SceneTreeDock::get_singleton()->set_edited_scene(current_edited_scene); + original_node->get_tree()->set_edited_scene_root(instantiated_node); + } + } + + // Replace the original node with the instantiated version. + original_node->replace_by(instantiated_node, false); + + // Mark the old node for deletion. + original_node->queue_free(); + + // Restore the folded and placeholder state from the original node. + instantiated_node->set_display_folded(original_node_is_displayed_folded); + instantiated_node->set_scene_instance_load_placeholder(original_node_scene_instance_load_placeholder); + + if (owner) { + Ref<SceneState> ss_inst = owner->get_scene_instance_state(); + if (ss_inst.is_valid()) { + ss_inst->update_instance_resource(p_instance_path, current_packed_scene); + } + + owner->set_editable_instance(instantiated_node, is_editable); + } + + // Attempt to re-add all the additional nodes. + for (AdditiveNodeEntry additive_node_entry : addition_list) { + Node *parent_node = instantiated_node->get_node_or_null(additive_node_entry.parent); + + if (!parent_node) { + parent_node = current_edited_scene; + } + + parent_node->add_child(additive_node_entry.node); + parent_node->move_child(additive_node_entry.node, additive_node_entry.index); + // If the additive node's owner was the node which got replaced, update it. + if (additive_node_entry.owner == original_node) { + additive_node_entry.owner = instantiated_node; + } + + additive_node_entry.node->set_owner(additive_node_entry.owner); + + // If the parent node was lost, attempt to restore the original global transform. + { + Node2D *node_2d = Object::cast_to<Node2D>(additive_node_entry.node); + if (node_2d) { + node_2d->set_transform(additive_node_entry.transform_2d); + } + + Node3D *node_3d = Object::cast_to<Node3D>(additive_node_entry.node); + if (node_3d) { + node_3d->set_transform(additive_node_entry.transform_3d); + } + } + } + + // Restore the selection. + if (selected_node_paths.size()) { + for (NodePath selected_node_path : selected_node_paths) { + Node *selected_node = instantiated_node->get_node_or_null(selected_node_path); + if (selected_node) { + editor_selection->add_node(selected_node); + } + } + editor_selection->update(); + } + + // Attempt to restore the modified properties and signals for the instantitated node and all its owned children. + for (KeyValue<NodePath, ModificationNodeEntry> &E : modification_table) { + NodePath new_current_path = E.key; + Node *modifiable_node = instantiated_node->get_node_or_null(new_current_path); + + if (modifiable_node) { + // Get properties for this node. + List<PropertyInfo> pinfo; + modifiable_node->get_property_list(&pinfo); + + // Get names of all valid property names (TODO: make this more efficent). + List<String> property_names; + for (const PropertyInfo &E2 : pinfo) { + if (E2.usage & PROPERTY_USAGE_STORAGE) { + property_names.push_back(E2.name); + } + } + + // Restore the modified properties for this node. + for (const KeyValue<StringName, Variant> &E2 : E.value.property_table) { + if (property_names.find(E2.key)) { + modifiable_node->set(E2.key, E2.value); + } + } + // Restore the connections to other nodes. + for (const ConnectionWithNodePath &E2 : E.value.connections_to) { + Connection conn = E2.connection; + + // Get the node the callable is targetting. + Node *target_node = cast_to<Node>(conn.callable.get_object()); + + // If the callable object no longer exists or is marked for deletion, + // attempt to reaccquire the closest match by using the node path + // we saved earlier. + if (!target_node || !target_node->is_queued_for_deletion()) { + target_node = modifiable_node->get_node_or_null(E2.node_path); + } + + if (target_node) { + // Reconstruct the callable. + Callable new_callable = Callable(target_node, conn.callable.get_method()); + + if (!modifiable_node->is_connected(conn.signal.get_name(), new_callable)) { + ERR_FAIL_COND(modifiable_node->connect(conn.signal.get_name(), new_callable, conn.flags) != OK); + } + } + } + + // Restore the connections from other nodes. + for (const Connection &E2 : E.value.connections_from) { + Connection conn = E2; + + bool valid = modifiable_node->has_method(conn.callable.get_method()) || Ref<Script>(modifiable_node->get_script()).is_null() || Ref<Script>(modifiable_node->get_script())->has_method(conn.callable.get_method()); + ERR_CONTINUE_MSG(!valid, vformat("Attempt to connect signal '%s.%s' to nonexistent method '%s.%s'.", conn.signal.get_object()->get_class(), conn.signal.get_name(), conn.callable.get_object()->get_class(), conn.callable.get_method())); + + // Get the object which the signal is connected from. + Object *source_object = conn.signal.get_object(); + + if (source_object) { + ERR_FAIL_COND(source_object->connect(conn.signal.get_name(), Callable(modifiable_node, conn.callable.get_method()), conn.flags) != OK); + } + } + + // Re-add the groups. + for (const Node::GroupInfo &E2 : E.value.groups) { + modifiable_node->add_to_group(E2.name, E2.persistent); + } + } + } + } + // Cleanup the history of the changes. + editor_history.cleanup_history(); + + current_edited_scene->propagate_notification(NOTIFICATION_NODE_RECACHE_REQUESTED); + } + edited_scene_map.clear(); + } + editor_data.set_edited_scene(original_edited_scene_idx); + + _edit_current(); +} + int EditorNode::plugin_init_callback_count = 0; void EditorNode::add_plugin_init_callback(EditorPluginInitializeCallback p_callback) { @@ -5951,7 +6430,7 @@ void EditorNode::_feature_profile_changed() { } void EditorNode::_bind_methods() { - GLOBAL_DEF(PropertyInfo(Variant::INT, "editor/scene/scene_naming", PROPERTY_HINT_ENUM, "Auto,PascalCase,snake_case"), SCENE_NAME_CASING_SNAKE_CASE); + GLOBAL_DEF(PropertyInfo(Variant::INT, "editor/naming/scene_name_casing", PROPERTY_HINT_ENUM, "Auto,PascalCase,snake_case"), SCENE_NAME_CASING_SNAKE_CASE); ClassDB::bind_method("edit_current", &EditorNode::edit_current); ClassDB::bind_method("edit_node", &EditorNode::edit_node); @@ -7401,6 +7880,11 @@ EditorNode::EditorNode() { EditorExport::get_singleton()->add_export_plugin(gdextension_export_plugin); + Ref<DedicatedServerExportPlugin> dedicated_server_export_plugin; + dedicated_server_export_plugin.instantiate(); + + EditorExport::get_singleton()->add_export_plugin(dedicated_server_export_plugin); + Ref<PackedSceneEditorTranslationParserPlugin> packed_scene_translation_parser_plugin; packed_scene_translation_parser_plugin.instantiate(); EditorTranslationParser::get_singleton()->add_parser(packed_scene_translation_parser_plugin, EditorTranslationParser::STANDARD); diff --git a/editor/editor_node.h b/editor/editor_node.h index ef4ea34da2..bb10abb589 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -87,6 +87,7 @@ class ImportDock; class LinkButton; class MenuBar; class MenuButton; +class Node2D; class NodeDock; class OptionButton; class OrphanResourcesDialog; @@ -818,6 +819,37 @@ public: Error load_scene(const String &p_scene, bool p_ignore_broken_deps = false, bool p_set_inherited = false, bool p_clear_errors = true, bool p_force_open_imported = false, bool p_silent_change_tab = false); Error load_resource(const String &p_resource, bool p_ignore_broken_deps = false); + HashMap<StringName, Variant> get_modified_properties_for_node(Node *p_node); + + struct AdditiveNodeEntry { + Node *node = nullptr; + NodePath parent = NodePath(); + Node *owner = nullptr; + int index = 0; + // Used if the original parent node is lost + Transform2D transform_2d; + Transform3D transform_3d; + }; + + struct ConnectionWithNodePath { + Connection connection; + NodePath node_path; + }; + + struct ModificationNodeEntry { + HashMap<StringName, Variant> property_table; + List<ConnectionWithNodePath> connections_to; + List<Connection> connections_from; + List<Node::GroupInfo> groups; + }; + + void update_diff_data_for_node( + Node *p_edited_scene, + Node *p_root, + Node *p_node, + HashMap<NodePath, ModificationNodeEntry> &p_modification_table, + List<AdditiveNodeEntry> &p_addition_list); + bool is_scene_open(const String &p_path); void set_current_scene(int p_idx); @@ -870,6 +902,9 @@ public: void reload_scene(const String &p_path); + void find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, List<Node *> &p_instance_list); + void reload_instances_with_path_in_edited_scenes(const String &p_path); + bool is_exiting() const { return exiting; } Button *get_pause_button() { return pause_button; } diff --git a/editor/event_listener_line_edit.cpp b/editor/event_listener_line_edit.cpp index 274fe34c52..ea4a7133bf 100644 --- a/editor/event_listener_line_edit.cpp +++ b/editor/event_listener_line_edit.cpp @@ -59,16 +59,42 @@ static const char *_joy_axis_descriptions[(size_t)JoyAxis::MAX * 2] = { String EventListenerLineEdit::get_event_text(const Ref<InputEvent> &p_event, bool p_include_device) { ERR_FAIL_COND_V_MSG(p_event.is_null(), String(), "Provided event is not a valid instance of InputEvent"); - String text = p_event->as_text(); - + String text; Ref<InputEventKey> key = p_event; - if (key.is_valid() && key->is_command_or_control_autoremap()) { + if (key.is_valid()) { + String mods_text = key->InputEventWithModifiers::as_text(); + mods_text = mods_text.is_empty() ? mods_text : mods_text + "+"; + if (key->is_command_or_control_autoremap()) { #ifdef MACOS_ENABLED - text = text.replace("Command", "Command/Ctrl"); + mods_text = mods_text.replace("Command", "Command/Ctrl"); #else - text = text.replace("Ctrl", "Command/Ctrl"); + mods_text = mods_text.replace("Ctrl", "Command/Ctrl"); #endif + } + + if (key->get_keycode() != Key::NONE) { + text += mods_text + keycode_get_string(key->get_keycode()); + } + if (key->get_physical_keycode() != Key::NONE) { + if (!text.is_empty()) { + text += " or "; + } + text += mods_text + keycode_get_string(key->get_physical_keycode()) + " (" + RTR("Physical") + ")"; + } + if (key->get_key_label() != Key::NONE) { + if (!text.is_empty()) { + text += " or "; + } + text += mods_text + keycode_get_string(key->get_key_label()) + " (Unicode)"; + } + + if (text.is_empty()) { + text = "(" + RTR("Unset") + ")"; + } + } else { + text = p_event->as_text(); } + Ref<InputEventMouse> mouse = p_event; Ref<InputEventJoypadMotion> jp_motion = p_event; Ref<InputEventJoypadButton> jp_button = p_event; diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp index 1d147cc5b9..4900ced2e4 100644 --- a/editor/export/editor_export.cpp +++ b/editor/export/editor_export.cpp @@ -45,6 +45,7 @@ void EditorExport::_save() { config->set_value(section, "name", preset->get_name()); config->set_value(section, "platform", preset->get_platform()->get_name()); config->set_value(section, "runnable", preset->is_runnable()); + config->set_value(section, "dedicated_server", preset->is_dedicated_server()); config->set_value(section, "custom_features", preset->get_custom_features()); bool save_files = false; @@ -64,6 +65,11 @@ void EditorExport::_save() { config->set_value(section, "export_filter", "exclude"); save_files = true; } break; + case EditorExportPreset::EXPORT_CUSTOMIZED: { + config->set_value(section, "export_filter", "customized"); + config->set_value(section, "customized_files", preset->get_customized_files()); + save_files = false; + }; } if (save_files) { @@ -213,6 +219,7 @@ void EditorExport::load_config() { preset->set_name(config->get_value(section, "name")); preset->set_runnable(config->get_value(section, "runnable")); + preset->set_dedicated_server(config->get_value(section, "dedicated_server", false)); if (config->has_section_key(section, "custom_features")) { preset->set_custom_features(config->get_value(section, "custom_features")); @@ -233,6 +240,10 @@ void EditorExport::load_config() { } else if (export_filter == "exclude") { preset->set_export_filter(EditorExportPreset::EXCLUDE_SELECTED_RESOURCES); get_files = true; + } else if (export_filter == "customized") { + preset->set_export_filter(EditorExportPreset::EXPORT_CUSTOMIZED); + preset->set_customized_files(config->get_value(section, "customized_files", Dictionary())); + get_files = false; } if (get_files) { diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index 8da52e20f7..9f79eecfb7 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -343,6 +343,24 @@ void EditorExportPlatform::_export_find_resources(EditorFileSystemDirectory *p_d } } +void EditorExportPlatform::_export_find_customized_resources(const Ref<EditorExportPreset> &p_preset, EditorFileSystemDirectory *p_dir, EditorExportPreset::FileExportMode p_mode, HashSet<String> &p_paths) { + for (int i = 0; i < p_dir->get_subdir_count(); i++) { + EditorFileSystemDirectory *subdir = p_dir->get_subdir(i); + _export_find_customized_resources(p_preset, subdir, p_preset->get_file_export_mode(subdir->get_path(), p_mode), p_paths); + } + + for (int i = 0; i < p_dir->get_file_count(); i++) { + if (p_dir->get_file_type(i) == "TextFile") { + continue; + } + String path = p_dir->get_file_path(i); + EditorExportPreset::FileExportMode file_mode = p_preset->get_file_export_mode(path, p_mode); + if (file_mode != EditorExportPreset::MODE_FILE_REMOVE) { + p_paths.insert(path); + } + } +} + void EditorExportPlatform::_export_find_dependencies(const String &p_path, HashSet<String> &p_paths) { if (p_paths.has(p_path)) { return; @@ -639,10 +657,20 @@ bool EditorExportPlatform::_export_customize_object(Object *p_object, LocalVecto return changed; } +bool EditorExportPlatform::_is_editable_ancestor(Node *p_root, Node *p_node) { + while (p_node != nullptr && p_node != p_root) { + if (p_root->is_editable_instance(p_node)) { + return true; + } + p_node = p_node->get_owner(); + } + return false; +} + bool EditorExportPlatform::_export_customize_scene_resources(Node *p_root, Node *p_node, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins) { bool changed = false; - if (p_node == p_root || p_node->get_owner() == p_root) { + if (p_root == p_node || p_node->get_owner() == p_root || _is_editable_ancestor(p_root, p_node)) { if (_export_customize_object(p_node, customize_resources_plugins)) { changed = true; } @@ -757,10 +785,10 @@ String EditorExportPlatform::_export_customize(const String &p_path, LocalVector break; } } + } - if (_export_customize_object(res.ptr(), customize_resources_plugins)) { - modified = true; - } + if (_export_customize_object(res.ptr(), customize_resources_plugins)) { + modified = true; } if (modified || p_force_save) { @@ -796,6 +824,8 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & for (int i = 0; i < files.size(); i++) { paths.erase(files[i]); } + } else if (p_preset->get_export_filter() == EditorExportPreset::EXPORT_CUSTOMIZED) { + _export_find_customized_resources(p_preset, EditorFileSystem::get_singleton()->get_filesystem(), p_preset->get_file_export_mode("res://"), paths); } else { bool scenes_only = p_preset->get_export_filter() == EditorExportPreset::EXPORT_SELECTED_SCENES; @@ -939,14 +969,14 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & LocalVector<Ref<EditorExportPlugin>> customize_scenes_plugins; for (int i = 0; i < export_plugins.size(); i++) { - if (export_plugins[i]->_begin_customize_resources(Ref<EditorExportPlatform>(this), features_psa)) { + if (export_plugins.write[i]->_begin_customize_resources(Ref<EditorExportPlatform>(this), features_psa)) { customize_resources_plugins.push_back(export_plugins[i]); custom_resources_hash = hash_murmur3_one_64(export_plugins[i]->_get_name().hash64(), custom_resources_hash); uint64_t hash = export_plugins[i]->_get_customization_configuration_hash(); custom_resources_hash = hash_murmur3_one_64(hash, custom_resources_hash); } - if (export_plugins[i]->_begin_customize_scenes(Ref<EditorExportPlatform>(this), features_psa)) { + if (export_plugins.write[i]->_begin_customize_scenes(Ref<EditorExportPlatform>(this), features_psa)) { customize_scenes_plugins.push_back(export_plugins[i]); custom_resources_hash = hash_murmur3_one_64(export_plugins[i]->_get_name().hash64(), custom_resources_hash); @@ -1218,6 +1248,9 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & } } } + for (int i = 0; i < export_plugins.size(); i++) { + custom_list.append_array(export_plugins[i]->_get_export_features(Ref<EditorExportPlatform>(this), p_debug)); + } ProjectSettings::CustomMap custom_map; if (path_remaps.size()) { @@ -1294,7 +1327,9 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & } else { // Use default text server data. String icu_data_file = EditorPaths::get_singleton()->get_cache_dir().path_join("tmp_icu_data"); - TS->save_support_data(icu_data_file); + if (!TS->save_support_data(icu_data_file)) { + return ERR_INVALID_DATA; + } Vector<uint8_t> array = FileAccess::get_file_as_bytes(icu_data_file); err = p_func(p_udata, ts_data, array, idx, total, enc_in_filters, enc_ex_filters, key); DirAccess::remove_file_or_error(icu_data_file); diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h index 1fb35759ac..3b4e92c9bd 100644 --- a/editor/export/editor_export_platform.h +++ b/editor/export/editor_export_platform.h @@ -91,6 +91,7 @@ private: Vector<ExportMessage> messages; void _export_find_resources(EditorFileSystemDirectory *p_dir, HashSet<String> &p_paths); + void _export_find_customized_resources(const Ref<EditorExportPreset> &p_preset, EditorFileSystemDirectory *p_dir, EditorExportPreset::FileExportMode p_mode, HashSet<String> &p_paths); void _export_find_dependencies(const String &p_path, HashSet<String> &p_paths); static Error _save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); @@ -112,6 +113,7 @@ private: bool _export_customize_array(Array &array, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins); bool _export_customize_object(Object *p_object, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins); bool _export_customize_scene_resources(Node *p_root, Node *p_node, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins); + bool _is_editable_ancestor(Node *p_root, Node *p_node); String _export_customize(const String &p_path, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins, LocalVector<Ref<EditorExportPlugin>> &customize_scenes_plugins, HashMap<String, FileExportCache> &export_cache, const String &export_base_path, bool p_force_save); diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp index dfd4520eec..0add55820f 100644 --- a/editor/export/editor_export_plugin.cpp +++ b/editor/export/editor_export_plugin.cpp @@ -141,7 +141,7 @@ void EditorExportPlugin::_export_end_script() { // Customization -bool EditorExportPlugin::_begin_customize_resources(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) const { +bool EditorExportPlugin::_begin_customize_resources(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) { bool ret = false; GDVIRTUAL_CALL(_begin_customize_resources, p_platform, p_features, ret); return ret; @@ -153,7 +153,7 @@ Ref<Resource> EditorExportPlugin::_customize_resource(const Ref<Resource> &p_res return ret; } -bool EditorExportPlugin::_begin_customize_scenes(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) const { +bool EditorExportPlugin::_begin_customize_scenes(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) { bool ret = false; GDVIRTUAL_CALL(_begin_customize_scenes, p_platform, p_features, ret); return ret; @@ -185,6 +185,12 @@ String EditorExportPlugin::_get_name() const { return ret; } +PackedStringArray EditorExportPlugin::_get_export_features(const Ref<EditorExportPlatform> &p_platform, bool p_debug) const { + PackedStringArray ret; + GDVIRTUAL_CALL(_get_export_features, p_platform, p_debug, ret); + return ret; +} + void EditorExportPlugin::_export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) { } diff --git a/editor/export/editor_export_plugin.h b/editor/export/editor_export_plugin.h index 5ac0a70c3e..fad647a67b 100644 --- a/editor/export/editor_export_plugin.h +++ b/editor/export/editor_export_plugin.h @@ -120,18 +120,22 @@ protected: GDVIRTUAL0(_end_customize_scenes) GDVIRTUAL0(_end_customize_resources) + GDVIRTUAL2RC(PackedStringArray, _get_export_features, const Ref<EditorExportPlatform> &, bool); + GDVIRTUAL0RC(String, _get_name) - bool _begin_customize_resources(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) const; // Return true if this plugin does property export customization - Ref<Resource> _customize_resource(const Ref<Resource> &p_resource, const String &p_path); // If nothing is returned, it means do not touch (nothing changed). If something is returned (either the same or a different resource) it means changes are made. + virtual bool _begin_customize_resources(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features); // Return true if this plugin does property export customization + virtual Ref<Resource> _customize_resource(const Ref<Resource> &p_resource, const String &p_path); // If nothing is returned, it means do not touch (nothing changed). If something is returned (either the same or a different resource) it means changes are made. + + virtual bool _begin_customize_scenes(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features); // Return true if this plugin does property export customization + virtual Node *_customize_scene(Node *p_root, const String &p_path); // Return true if a change was made - bool _begin_customize_scenes(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) const; // Return true if this plugin does property export customization - Node *_customize_scene(Node *p_root, const String &p_path); // Return true if a change was made + virtual uint64_t _get_customization_configuration_hash() const; // Hash used for caching customized resources and scenes. - uint64_t _get_customization_configuration_hash() const; // Hash used for caching customized resources and scenes. + virtual void _end_customize_scenes(); + virtual void _end_customize_resources(); - void _end_customize_scenes(); - void _end_customize_resources(); + virtual PackedStringArray _get_export_features(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const; virtual String _get_name() const; diff --git a/editor/export/editor_export_preset.cpp b/editor/export/editor_export_preset.cpp index c6365806b3..6beef623bc 100644 --- a/editor/export/editor_export_preset.cpp +++ b/editor/export/editor_export_preset.cpp @@ -64,15 +64,29 @@ Ref<EditorExportPlatform> EditorExportPreset::get_platform() const { return platform; } -void EditorExportPreset::update_files_to_export() { - Vector<String> to_remove; - for (const String &E : selected_files) { - if (!FileAccess::exists(E)) { - to_remove.push_back(E); +void EditorExportPreset::update_files() { + { + Vector<String> to_remove; + for (const String &E : selected_files) { + if (!FileAccess::exists(E)) { + to_remove.push_back(E); + } + } + for (int i = 0; i < to_remove.size(); ++i) { + selected_files.erase(to_remove[i]); } } - for (int i = 0; i < to_remove.size(); ++i) { - selected_files.erase(to_remove[i]); + + { + Vector<String> to_remove; + for (const KeyValue<String, FileExportMode> &E : customized_files) { + if (!FileAccess::exists(E.key) && !DirAccess::exists(E.key)) { + to_remove.push_back(E.key); + } + } + for (int i = 0; i < to_remove.size(); ++i) { + customized_files.erase(to_remove[i]); + } } } @@ -84,6 +98,48 @@ Vector<String> EditorExportPreset::get_files_to_export() const { return files; } +Dictionary EditorExportPreset::get_customized_files() const { + Dictionary files; + for (const KeyValue<String, FileExportMode> &E : customized_files) { + String mode; + switch (E.value) { + case MODE_FILE_NOT_CUSTOMIZED: { + continue; + } break; + case MODE_FILE_STRIP: { + mode = "strip"; + } break; + case MODE_FILE_KEEP: { + mode = "keep"; + } break; + case MODE_FILE_REMOVE: { + mode = "remove"; + } + } + files[E.key] = mode; + } + return files; +} + +int EditorExportPreset::get_customized_files_count() const { + return customized_files.size(); +} + +void EditorExportPreset::set_customized_files(const Dictionary &p_files) { + for (const Variant *key = p_files.next(nullptr); key; key = p_files.next(key)) { + EditorExportPreset::FileExportMode mode = EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED; + String value = p_files[*key]; + if (value == "strip") { + mode = EditorExportPreset::MODE_FILE_STRIP; + } else if (value == "keep") { + mode = EditorExportPreset::MODE_FILE_KEEP; + } else if (value == "remove") { + mode = EditorExportPreset::MODE_FILE_REMOVE; + } + set_file_export_mode(*key, mode); + } +} + void EditorExportPreset::set_name(const String &p_name) { name = p_name; EditorExport::singleton->save_presets(); @@ -102,6 +158,15 @@ bool EditorExportPreset::is_runnable() const { return runnable; } +void EditorExportPreset::set_dedicated_server(bool p_enable) { + dedicated_server = p_enable; + EditorExport::singleton->save_presets(); +} + +bool EditorExportPreset::is_dedicated_server() const { + return dedicated_server; +} + void EditorExportPreset::set_export_filter(ExportFilter p_filter) { export_filter = p_filter; EditorExport::singleton->save_presets(); @@ -158,6 +223,23 @@ bool EditorExportPreset::has_export_file(const String &p_path) { return selected_files.has(p_path); } +void EditorExportPreset::set_file_export_mode(const String &p_path, EditorExportPreset::FileExportMode p_mode) { + if (p_mode == FileExportMode::MODE_FILE_NOT_CUSTOMIZED) { + customized_files.erase(p_path); + } else { + customized_files.insert(p_path, p_mode); + } + EditorExport::singleton->save_presets(); +} + +EditorExportPreset::FileExportMode EditorExportPreset::get_file_export_mode(const String &p_path, EditorExportPreset::FileExportMode p_default) const { + HashMap<String, FileExportMode>::ConstIterator i = customized_files.find(p_path); + if (i) { + return i->value; + } + return p_default; +} + void EditorExportPreset::set_custom_features(const String &p_custom_features) { custom_features = p_custom_features; EditorExport::singleton->save_presets(); diff --git a/editor/export/editor_export_preset.h b/editor/export/editor_export_preset.h index de208f410e..db139d8860 100644 --- a/editor/export/editor_export_preset.h +++ b/editor/export/editor_export_preset.h @@ -44,6 +44,14 @@ public: EXPORT_SELECTED_SCENES, EXPORT_SELECTED_RESOURCES, EXCLUDE_SELECTED_RESOURCES, + EXPORT_CUSTOMIZED, + }; + + enum FileExportMode { + MODE_FILE_NOT_CUSTOMIZED, + MODE_FILE_STRIP, + MODE_FILE_KEEP, + MODE_FILE_REMOVE, }; private: @@ -55,7 +63,9 @@ private: String exporter; HashSet<String> selected_files; + HashMap<String, FileExportMode> customized_files; bool runnable = false; + bool dedicated_server = false; friend class EditorExport; friend class EditorExportPlatform; @@ -85,20 +95,29 @@ public: bool has(const StringName &p_property) const { return values.has(p_property); } - void update_files_to_export(); + void update_files(); Vector<String> get_files_to_export() const; + Dictionary get_customized_files() const; + int get_customized_files_count() const; + void set_customized_files(const Dictionary &p_files); void add_export_file(const String &p_path); void remove_export_file(const String &p_path); bool has_export_file(const String &p_path); + void set_file_export_mode(const String &p_path, FileExportMode p_mode); + FileExportMode get_file_export_mode(const String &p_path, FileExportMode p_default = MODE_FILE_NOT_CUSTOMIZED) const; + void set_name(const String &p_name); String get_name() const; void set_runnable(bool p_enable); bool is_runnable() const; + void set_dedicated_server(bool p_enable); + bool is_dedicated_server() const; + void set_export_filter(ExportFilter p_filter); ExportFilter get_export_filter() const; diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp index 2caebc6f04..52c192164f 100644 --- a/editor/export/project_export.cpp +++ b/editor/export/project_export.cpp @@ -45,6 +45,7 @@ #include "scene/gui/link_button.h" #include "scene/gui/menu_button.h" #include "scene/gui/option_button.h" +#include "scene/gui/popup_menu.h" #include "scene/gui/split_container.h" #include "scene/gui/texture_rect.h" #include "scene/gui/tree.h" @@ -165,7 +166,7 @@ void ProjectExportDialog::_update_presets() { if (preset->is_runnable()) { preset_name += " (" + TTR("Runnable") + ")"; } - preset->update_files_to_export(); + preset->update_files(); presets->add_item(preset_name, preset->get_platform()->get_logo()); } @@ -244,6 +245,7 @@ void ProjectExportDialog::_edit_preset(int p_index) { export_filter->select(current->get_export_filter()); include_filters->set_text(current->get_include_filter()); exclude_filters->set_text(current->get_exclude_filter()); + server_strip_message->set_visible(current->get_export_filter() == EditorExportPreset::EXPORT_CUSTOMIZED); _fill_resource_tree(); @@ -570,6 +572,7 @@ void ProjectExportDialog::_duplicate_preset() { if (make_runnable) { preset->set_runnable(make_runnable); } + preset->set_dedicated_server(current->is_dedicated_server()); preset->set_export_filter(current->get_export_filter()); preset->set_include_filter(current->get_include_filter()); preset->set_exclude_filter(current->get_exclude_filter()); @@ -692,7 +695,16 @@ void ProjectExportDialog::_export_type_changed(int p_which) { return; } - current->set_export_filter(EditorExportPreset::ExportFilter(p_which)); + EditorExportPreset::ExportFilter filter_type = (EditorExportPreset::ExportFilter)p_which; + current->set_export_filter(filter_type); + current->set_dedicated_server(filter_type == EditorExportPreset::EXPORT_CUSTOMIZED); + server_strip_message->set_visible(filter_type == EditorExportPreset::EXPORT_CUSTOMIZED); + + // Default to stripping everything when first switching to server build. + if (filter_type == EditorExportPreset::EXPORT_CUSTOMIZED && current->get_customized_files_count() == 0) { + current->set_file_export_mode("res://", EditorExportPreset::MODE_FILE_STRIP); + } + updating = true; _fill_resource_tree(); updating = false; @@ -728,25 +740,53 @@ void ProjectExportDialog::_fill_resource_tree() { return; } + TreeItem *root = include_files->create_item(); + + if (f == EditorExportPreset::EXPORT_CUSTOMIZED) { + include_files->set_columns(2); + include_files->set_column_expand(1, false); + include_files->set_column_custom_minimum_width(1, 250 * EDSCALE); + } else { + include_files->set_columns(1); + } + include_label->show(); include_margin->show(); - TreeItem *root = include_files->create_item(); + _fill_tree(EditorFileSystem::get_singleton()->get_filesystem(), root, current, f); +} - _fill_tree(EditorFileSystem::get_singleton()->get_filesystem(), root, current, f == EditorExportPreset::EXPORT_SELECTED_SCENES); +void ProjectExportDialog::_setup_item_for_file_mode(TreeItem *p_item, EditorExportPreset::FileExportMode p_mode) { + if (p_mode == EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED) { + p_item->set_checked(0, false); + p_item->set_cell_mode(1, TreeItem::CELL_MODE_STRING); + p_item->set_text(1, ""); + p_item->set_editable(1, false); + p_item->set_selectable(1, false); + } else { + p_item->set_checked(0, true); + p_item->set_cell_mode(1, TreeItem::CELL_MODE_CUSTOM); + p_item->set_text(1, file_mode_popup->get_item_text(file_mode_popup->get_item_index(p_mode))); + p_item->set_editable(1, true); + p_item->set_selectable(1, true); + } } -bool ProjectExportDialog::_fill_tree(EditorFileSystemDirectory *p_dir, TreeItem *p_item, Ref<EditorExportPreset> ¤t, bool p_only_scenes) { +bool ProjectExportDialog::_fill_tree(EditorFileSystemDirectory *p_dir, TreeItem *p_item, Ref<EditorExportPreset> ¤t, EditorExportPreset::ExportFilter p_export_filter) { p_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); p_item->set_icon(0, presets->get_theme_icon(SNAME("folder"), SNAME("FileDialog"))); p_item->set_text(0, p_dir->get_name() + "/"); p_item->set_editable(0, true); p_item->set_metadata(0, p_dir->get_path()); + if (p_export_filter == EditorExportPreset::EXPORT_CUSTOMIZED) { + _setup_item_for_file_mode(p_item, current->get_file_export_mode(p_dir->get_path())); + } + bool used = false; for (int i = 0; i < p_dir->get_subdir_count(); i++) { TreeItem *subdir = include_files->create_item(p_item); - if (_fill_tree(p_dir->get_subdir(i), subdir, current, p_only_scenes)) { + if (_fill_tree(p_dir->get_subdir(i), subdir, current, p_export_filter)) { used = true; } else { memdelete(subdir); @@ -755,7 +795,7 @@ bool ProjectExportDialog::_fill_tree(EditorFileSystemDirectory *p_dir, TreeItem for (int i = 0; i < p_dir->get_file_count(); i++) { String type = p_dir->get_file_type(i); - if (p_only_scenes && type != "PackedScene") { + if (p_export_filter == EditorExportPreset::EXPORT_SELECTED_SCENES && type != "PackedScene") { continue; } if (type == "TextFile") { @@ -770,9 +810,14 @@ bool ProjectExportDialog::_fill_tree(EditorFileSystemDirectory *p_dir, TreeItem file->set_icon(0, EditorNode::get_singleton()->get_class_icon(type)); file->set_editable(0, true); - file->set_checked(0, current->has_export_file(path)); file->set_metadata(0, path); - file->propagate_check(0); + + if (p_export_filter == EditorExportPreset::EXPORT_CUSTOMIZED) { + _setup_item_for_file_mode(file, current->get_file_export_mode(path)); + } else { + file->set_checked(0, current->has_export_file(path)); + file->propagate_check(0); + } used = true; } @@ -794,7 +839,19 @@ void ProjectExportDialog::_tree_changed() { return; } - item->propagate_check(0); + if (current->get_export_filter() == EditorExportPreset::EXPORT_CUSTOMIZED) { + EditorExportPreset::FileExportMode file_mode = EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED; + String path = item->get_metadata(0); + + if (item->is_checked(0)) { + file_mode = current->get_file_export_mode(path, EditorExportPreset::MODE_FILE_STRIP); + } + + _setup_item_for_file_mode(item, file_mode); + current->set_file_export_mode(path, file_mode); + } else { + item->propagate_check(0); + } } void ProjectExportDialog::_check_propagated_to_item(Object *p_obj, int column) { @@ -814,6 +871,30 @@ void ProjectExportDialog::_check_propagated_to_item(Object *p_obj, int column) { } } +void ProjectExportDialog::_tree_popup_edited(bool p_arrow_clicked) { + Rect2 bounds = include_files->get_custom_popup_rect(); + bounds.position += get_global_canvas_transform().get_origin(); + bounds.size *= get_global_canvas_transform().get_scale(); + if (!is_embedding_subwindows()) { + bounds.position += get_position(); + } + file_mode_popup->popup(bounds); +} + +void ProjectExportDialog::_set_file_export_mode(int p_id) { + Ref<EditorExportPreset> current = get_current_preset(); + if (current.is_null()) { + return; + } + + TreeItem *item = include_files->get_edited(); + String path = item->get_metadata(0); + + current->set_file_export_mode(path, (EditorExportPreset::FileExportMode)p_id); + + item->set_text(1, file_mode_popup->get_item_text(file_mode_popup->get_item_index(p_id))); +} + void ProjectExportDialog::_export_pck_zip() { Ref<EditorExportPreset> current = get_current_preset(); ERR_FAIL_COND(current.is_null()); @@ -1068,6 +1149,7 @@ ProjectExportDialog::ProjectExportDialog() { export_filter->add_item(TTR("Export selected scenes (and dependencies)")); export_filter->add_item(TTR("Export selected resources (and dependencies)")); export_filter->add_item(TTR("Export all resources in the project except resources checked below")); + export_filter->add_item(TTR("Export as dedicated server")); resources_vb->add_margin_child(TTR("Export Mode:"), export_filter); export_filter->connect("item_selected", callable_mp(this, &ProjectExportDialog::_export_type_changed)); @@ -1082,6 +1164,36 @@ ProjectExportDialog::ProjectExportDialog() { include_margin->add_child(include_files); include_files->connect("item_edited", callable_mp(this, &ProjectExportDialog::_tree_changed)); include_files->connect("check_propagated_to_item", callable_mp(this, &ProjectExportDialog::_check_propagated_to_item)); + include_files->connect("custom_popup_edited", callable_mp(this, &ProjectExportDialog::_tree_popup_edited)); + + server_strip_message = memnew(Label); + server_strip_message->set_visible(false); + server_strip_message->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); + resources_vb->add_child(server_strip_message); + + { + List<StringName> resource_names; + ClassDB::get_inheriters_from_class("Resource", &resource_names); + + PackedStringArray strippable; + for (StringName resource_name : resource_names) { + if (ClassDB::has_method(resource_name, "create_placeholder", true)) { + strippable.push_back(resource_name); + } + } + strippable.sort(); + + String message = TTR("\"Strip Visuals\" will replace the following resources with placeholders:") + " "; + message += String(", ").join(strippable); + server_strip_message->set_text(message); + } + + file_mode_popup = memnew(PopupMenu); + add_child(file_mode_popup); + file_mode_popup->add_item(TTR("Strip Visuals"), EditorExportPreset::MODE_FILE_STRIP); + file_mode_popup->add_item(TTR("Keep"), EditorExportPreset::MODE_FILE_KEEP); + file_mode_popup->add_item(TTR("Remove"), EditorExportPreset::MODE_FILE_REMOVE); + file_mode_popup->connect("id_pressed", callable_mp(this, &ProjectExportDialog::_set_file_export_mode)); include_filters = memnew(LineEdit); resources_vb->add_margin_child( diff --git a/editor/export/project_export.h b/editor/export/project_export.h index d392dba2a4..63f8fc4a2e 100644 --- a/editor/export/project_export.h +++ b/editor/export/project_export.h @@ -31,11 +31,11 @@ #ifndef PROJECT_EXPORT_H #define PROJECT_EXPORT_H +#include "editor/export/editor_export_preset.h" #include "scene/gui/dialogs.h" class CheckBox; class CheckButton; -class EditorExportPreset; class EditorFileDialog; class EditorFileSystemDirectory; class EditorInspector; @@ -43,6 +43,7 @@ class EditorPropertyPath; class ItemList; class MenuButton; class OptionButton; +class PopupMenu; class RichTextLabel; class TabContainer; class Tree; @@ -75,6 +76,8 @@ private: LineEdit *include_filters = nullptr; LineEdit *exclude_filters = nullptr; Tree *include_files = nullptr; + Label *server_strip_message = nullptr; + PopupMenu *file_mode_popup = nullptr; Label *include_label = nullptr; MarginContainer *include_margin = nullptr; @@ -113,9 +116,12 @@ private: void _export_type_changed(int p_which); void _filter_changed(const String &p_filter); void _fill_resource_tree(); - bool _fill_tree(EditorFileSystemDirectory *p_dir, TreeItem *p_item, Ref<EditorExportPreset> ¤t, bool p_only_scenes); + void _setup_item_for_file_mode(TreeItem *p_item, EditorExportPreset::FileExportMode p_mode); + bool _fill_tree(EditorFileSystemDirectory *p_dir, TreeItem *p_item, Ref<EditorExportPreset> ¤t, EditorExportPreset::ExportFilter p_export_filter); void _tree_changed(); void _check_propagated_to_item(Object *p_obj, int column); + void _tree_popup_edited(bool p_arrow_clicked); + void _set_file_export_mode(int p_id); Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index a078c58e72..378e06b4c9 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -1531,6 +1531,10 @@ void FileSystemDock::_make_scene_confirm() { EditorNode::get_singleton()->save_scene_list({ scene_path }); } +void FileSystemDock::_resource_removed(const Ref<Resource> &p_resource) { + emit_signal(SNAME("resource_removed"), p_resource); +} + void FileSystemDock::_file_removed(String p_file) { emit_signal(SNAME("file_removed"), p_file); @@ -3095,6 +3099,7 @@ void FileSystemDock::_bind_methods() { ADD_SIGNAL(MethodInfo("inherit", PropertyInfo(Variant::STRING, "file"))); ADD_SIGNAL(MethodInfo("instantiate", PropertyInfo(Variant::PACKED_STRING_ARRAY, "files"))); + ADD_SIGNAL(MethodInfo("resource_removed", PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"))); ADD_SIGNAL(MethodInfo("file_removed", PropertyInfo(Variant::STRING, "file"))); ADD_SIGNAL(MethodInfo("folder_removed", PropertyInfo(Variant::STRING, "folder"))); ADD_SIGNAL(MethodInfo("files_moved", PropertyInfo(Variant::STRING, "old_file"), PropertyInfo(Variant::STRING, "new_file"))); @@ -3254,6 +3259,7 @@ FileSystemDock::FileSystemDock() { add_child(owners_editor); remove_dialog = memnew(DependencyRemoveDialog); + remove_dialog->connect("resource_removed", callable_mp(this, &FileSystemDock::_resource_removed)); remove_dialog->connect("file_removed", callable_mp(this, &FileSystemDock::_file_removed)); remove_dialog->connect("folder_removed", callable_mp(this, &FileSystemDock::_folder_removed)); add_child(remove_dialog); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index 42a72b7ee8..ede6869eea 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -227,6 +227,7 @@ private: void _update_favorites_list_after_move(const HashMap<String, String> &p_files_renames, const HashMap<String, String> &p_folders_renames) const; void _update_project_settings_after_move(const HashMap<String, String> &p_renames) const; + void _resource_removed(const Ref<Resource> &p_resource); void _file_removed(String p_file); void _folder_removed(String p_folder); diff --git a/editor/icons/BoneMapHumanBody.svg b/editor/icons/BoneMapHumanBody.svg index 8674157aaa..818ee63069 100644 --- a/editor/icons/BoneMapHumanBody.svg +++ b/editor/icons/BoneMapHumanBody.svg @@ -1,26 +1 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<svg version="1.1" id="レイヤー_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" - y="0px" width="1024px" height="1024px" viewBox="0 0 1024 1024" enable-background="new 0 0 1024 1024" xml:space="preserve"> -<path fill="#3F3F3F" d="M0,0h1024v1024H0V0z"/> -<path fill="#B2B2B2" d="M512,536.162c7,35,11.645,66.898,14,114c2,40,4,51,2,66c-7.384,55.369,6.77,183.898,8.666,206.667 - c2,24-7.653,24.241-10.666,46.333c-2.449,17.958,79,18.439,65-9c-25-49-2-84,4-221c0.521-11.921-8.967-47.874-2-94 - c11.086-73.414,8.42-107.242,6.5-145.662c-1.245-31.973-1-56.963-9-138.963c-0.976-10.002,5.915-79.268,11.954-79.088 - c42,1.25,97.313-5.009,118.145-14.68c28.901,3.73,97.81-12.047,127.887-16.126c14.541,9.407,16.673,3.335,37.515,9.019 - c5.5,1.5,17.336-1.443,12-5c-7.409-4.937-20.75-8.25-23-12c10.75-2.5,22.365-9.578,36-13.166c9.5-2.5,18.866-11.748,15.5-12.334l0,0 - c-11.5-2-26.03,4.547-37.5,6.5c-15.724,2.678-25.238,3.24-33.334,5.167c-1.227,0.292-3.103,0.763-5.791,0.958 - c0,0-0.02,0.16-0.053,0.437c-36.818,0.994-80.322-9.724-130.31-5.569c-34.026-3.925-94.181-5.16-113.513-5.493 - c-13.911-0.239-59.293-2.583-71.75-0.5c-0.668-4.083-1.5-9.75,0.949-16.468c14.881-7.246,19.188-17.796,27.301-34.694 - c0.922,4.424,6.252,4.929,12.459-14.231c5.661-17.478,2.323-22.254-2.313-22.525c0.172-2.056,0.279-4.105,0.313-6.142 - C573.746,76.562,566,42.163,512,42.163s-61.746,34.399-60.959,82.44c0.034,2.037,0.142,4.086,0.313,6.142 - c-4.637,0.271-7.975,5.047-2.313,22.525c6.207,19.16,11.537,18.655,12.459,14.231c8.112,16.898,12.42,27.448,27.301,34.694 - c2.449,6.718,1.617,12.385,0.949,16.468c-12.457-2.083-57.839,0.261-71.75,0.5c-19.332,0.333-79.486,1.568-113.513,5.493 - c-49.987-4.155-93.491,6.563-130.31,5.569c-0.033-0.277-0.053-0.437-0.053-0.437c-2.688-0.195-4.564-0.666-5.791-0.958 - c-8.096-1.927-17.61-2.489-33.334-5.167c-11.47-1.953-26-8.5-37.5-6.5l0,0c-3.366,0.586,6,9.834,15.5,12.334 - c13.635,3.588,25.25,10.666,36,13.166c-2.25,3.75-15.591,7.063-23,12c-5.336,3.557,6.5,6.5,12,5 - c20.842-5.684,22.974,0.388,37.515-9.019c30.077,4.079,98.985,19.857,127.887,16.126c20.832,9.671,76.145,15.93,118.145,14.68 - c6.039-0.18,12.93,69.085,11.954,79.088c-8,82-7.755,106.99-9,138.963c-1.92,38.419-4.586,72.248,6.5,145.662 - c6.967,46.126-2.521,82.079-2,94c6,137,29,172,4,221c-14,27.439,67.449,26.958,65,9c-3.013-22.092-12.666-22.333-10.666-46.333 - c1.896-22.769,16.05-151.298,8.666-206.667c-2-15,0-26,2-66C500.356,603.061,505,571.162,512,536.162z"/> -</svg> +<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_1451_455)"><path d="M0 0H256V256H0V0Z" fill="#3F3F3F"/><path d="M128 134.04C129.75 142.79 130.911 150.765 131.5 162.54C132 172.54 132.5 175.29 132 179.04C130.154 192.883 133.692 225.015 134.166 230.707C134.666 236.707 132.253 236.767 131.5 242.29C130.888 246.78 151.25 246.9 147.75 240.04C141.5 227.79 147.25 219.04 148.75 184.79C148.88 181.81 146.508 172.822 148.25 161.29C151.021 142.937 150.355 134.48 149.875 124.875C149.564 116.882 149.625 110.634 147.625 90.134C147.381 87.6335 149.104 70.317 150.613 70.362C161.113 70.6745 174.942 69.1098 180.15 66.692C187.375 67.6245 204.602 63.6803 212.121 62.6605C215.757 65.0123 216.29 63.4943 221.5 64.9153C222.875 65.2903 225.834 64.5545 224.5 63.6653C222.648 62.431 219.313 61.6028 218.75 60.6653C221.438 60.0403 224.341 58.2708 227.75 57.3738C230.125 56.7488 232.467 54.4368 231.625 54.2903C228.75 53.7903 225.118 55.427 222.25 55.9153C218.319 56.5848 215.941 56.7253 213.917 57.207C213.61 57.28 213.141 57.3978 212.469 57.4465C212.469 57.4465 212.464 57.4865 212.456 57.5558C203.251 57.8043 192.375 55.1248 179.878 56.1635C171.372 55.1823 156.333 54.8735 151.5 54.7903C148.022 54.7305 136.677 54.1445 133.562 54.6653C133.395 53.6445 133.187 52.2278 133.8 50.5483C137.52 48.7368 138.597 46.0993 140.625 41.8748C140.855 42.9808 142.188 43.107 143.74 38.317C145.155 33.9475 144.32 32.7535 143.161 32.6858C143.204 32.1718 143.231 31.6595 143.24 31.1503C143.436 19.1403 141.5 10.5405 128 10.5405C114.5 10.5405 112.563 19.1403 112.76 31.1505C112.769 31.6598 112.796 32.172 112.838 32.686C111.679 32.7538 110.845 33.9478 112.26 38.3173C113.812 43.1073 115.144 42.981 115.375 41.875C117.403 46.0995 118.48 48.737 122.2 50.5485C122.812 52.228 122.604 53.6448 122.437 54.6655C119.323 54.1448 107.978 54.7308 104.5 54.7905C99.6669 54.8738 84.6284 55.1825 76.1217 56.1638C63.6249 55.125 52.7489 57.8045 43.5442 57.556C43.5359 57.4868 43.5309 57.4468 43.5309 57.4468C42.8589 57.398 42.3899 57.2803 42.0832 57.2073C40.0592 56.7255 37.6807 56.585 33.7497 55.9155C30.8822 55.4273 27.2497 53.7905 24.3747 54.2905C23.5332 54.437 25.8747 56.749 28.2497 57.374C31.6584 58.271 34.5622 60.0405 37.2497 60.6655C36.6872 61.603 33.3519 62.4313 31.4997 63.6655C30.1657 64.5548 33.1247 65.2905 34.4997 64.9155C39.7102 63.4945 40.2432 65.0125 43.8784 62.6608C51.3977 63.6805 68.6247 67.625 75.8502 66.6923C81.0582 69.11 94.8864 70.6748 105.386 70.3623C106.896 70.3173 108.619 87.6335 108.375 90.1343C106.375 110.634 106.436 116.882 106.125 124.875C105.645 134.48 104.978 142.937 107.75 161.291C109.492 172.822 107.12 181.81 107.25 184.791C108.75 219.041 114.5 227.791 108.25 240.041C104.75 246.9 125.112 246.78 124.5 242.291C123.747 236.768 121.333 236.707 121.833 230.707C122.307 225.015 125.846 192.883 124 179.041C123.5 175.291 124 172.541 124.5 162.541C125.089 150.765 126.25 142.79 128 134.04Z" fill="#B2B2B2"/></g><defs><clipPath id="clip0_1451_455"><rect width="256" height="256" fill="white"/></clipPath></defs></svg> diff --git a/editor/icons/BoneMapHumanFace.svg b/editor/icons/BoneMapHumanFace.svg index 6cb21140bc..e38c5cb790 100644 --- a/editor/icons/BoneMapHumanFace.svg +++ b/editor/icons/BoneMapHumanFace.svg @@ -1 +1 @@ -<svg enable-background="new 0 0 1024 1024" height="1024" viewBox="0 0 1024 1024" width="1024" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h1024v1024h-1024z" fill="#3f3f3f"/><path d="m788.105 552.967c17.995-57.892 31.896-124.566 30.875-198.071-3.758-270.403-249.846-251.479-295.568-244.947-359.868 51.409-219.047 452.358-220.453 496.426-4.899 153.499 83.686 170.991 161.665 215.554 2.646 1.512 7.259 1.786 13.313 1.111 7.223 25.179 11.762 59.035 9.548 75.638-3.266 24.495 209.021 24.495 209.021 0 0-62.883 12.233-124.363 33.827-188.89 7.143-2.284 16.054-7.601 25.963-16.95 13.681-12.908 34.839-21.774 45.726-63.145 15.615-59.338 3.869-76.074-13.917-76.726z" fill="#b2b2b2"/></svg> +<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_1451_458)"><path d="M0 0H256V256H0V0Z" fill="#3F3F3F"/><path d="M197.026 138.242C201.525 123.769 205 107.1 204.745 88.724C203.805 21.1232 142.283 25.8542 130.853 27.4872C40.8859 40.3395 76.0912 140.577 75.7397 151.594C74.5149 189.968 96.6612 194.342 116.156 205.482C116.817 205.86 117.971 205.929 119.484 205.76C121.29 212.055 122.425 220.519 121.871 224.669C121.055 230.793 174.126 230.793 174.126 224.669C174.126 208.949 177.185 193.579 182.583 177.447C184.369 176.876 186.597 175.547 189.074 173.21C192.494 169.983 197.784 167.766 200.505 157.423C204.409 142.589 201.473 138.405 197.026 138.242Z" fill="#B2B2B2"/></g><defs><clipPath id="clip0_1451_458"><rect width="256" height="256" fill="white"/></clipPath></defs></svg> diff --git a/editor/icons/BoneMapHumanLeftHand.svg b/editor/icons/BoneMapHumanLeftHand.svg index 08c68bb4be..a9861f818c 100644 --- a/editor/icons/BoneMapHumanLeftHand.svg +++ b/editor/icons/BoneMapHumanLeftHand.svg @@ -1 +1 @@ -<svg enable-background="new 0 0 1024 1024" height="1024" viewBox="0 0 1024 1024" width="1024" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h1024v1024h-1024z" fill="#3f3f3f"/><path d="m703.906 786.098c7.046-66.929 28.135-153.363 18.529-260.192-1.143-12.71-4.5-48.282-4.46-82.732.025-21.174-2.111-48.505-1.975-64.174.167-19.333-.428-41.584-.625-55.755-1.052-75.44-13.225-85.827-30.813-85.827-17.246 0-26.77 14.266-27.062 84.582-.061 14.42.5 51 .5 58.5 0 17.508-.333 34.167 0 53.5.447 25.955-4.279 68-9 68-3.902 0-8.099-39.299-9.575-76.999-.756-19.326-3.219-58.336-2.6-70.102 1.759-33.413.474-58.914 1.537-90.165 3.183-93.607-13.016-111.729-34.695-111.729-21.973 0-35.979 57.688-34.849 114.224.128 6.394-1.165 50.739.188 89.859.754 21.811-1.07 49.627-1.683 69.67-1.095 35.768-5.755 63.896-8.869 63.896-2.641 0-4.135-32.584-5.456-65.706-.859-21.557-4.468-58.477-3.664-83.616 1.886-59.012-1.139-110.226-1.063-121.501.635-94.955-14.66-123.101-36.052-123.101-21.476 0-37.188 30.192-36.6 123.343.067 10.53-2.62 99.926-1.759 121.816.865 21.992-2.773 65.062-3.517 84.818-1.299 34.521-6.49 63.947-9.124 63.947-3.281 0-10.794-25.638-11.724-60.965-.587-22.275 1.231-50.99.624-70.688-1.257-40.707-3.175-64.631-3.877-99.708-1.945-97.182-16.352-106.289-38.142-106.289-17.957 0-32.453 28.673-32.657 115.03-.065 27.702-2.429 62.626-.315 94.329.805 12.081-.622 42.512-1.875 73.894-.799 20.007-1.102 47.501-1.137 63.775-.17 78.595-26.712 133.424-36.555 131.308-30.333-6.521-51.648-43.918-71.219-117.307-10.551-39.566-36.667-71.149-69.9-77.813-25.9-5.193-19.783 46.161-1.319 125.293 8.65 37.068 27.909 86.227 39.566 122.655 31.653 98.917 125.574 188.563 160.903 228.546 17.146 19.403 236.894 19.403 264.59 0 11.525-8.07 43.087-101.557 45.724-126.616z" fill="#b2b2b2"/></svg> +<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_1451_461)"><path d="M0 0H256V256H0V0Z" fill="#3F3F3F"/><path d="M175.976 196.525C177.738 179.792 183.01 158.184 180.609 131.477C180.323 128.299 179.484 119.406 179.494 110.794C179.5 105.5 178.966 98.6674 179 94.7501C179.042 89.9169 178.893 84.3541 178.844 80.8114C178.581 61.9514 175.537 59.3546 171.14 59.3546C166.829 59.3546 164.448 62.9211 164.375 80.5001C164.36 84.1051 164.5 93.2501 164.5 95.1251C164.5 99.5021 164.417 103.667 164.5 108.5C164.612 114.989 163.43 125.5 162.25 125.5C161.274 125.5 160.225 115.675 159.856 106.25C159.667 101.419 159.051 91.6664 159.206 88.7249C159.646 80.3716 159.325 73.9964 159.59 66.1836C160.386 42.7819 156.336 38.2514 150.917 38.2514C145.423 38.2514 141.922 52.6734 142.204 66.8074C142.236 68.4059 141.913 79.4921 142.251 89.2721C142.44 94.7249 141.984 101.679 141.831 106.69C141.557 115.632 140.392 122.664 139.613 122.664C138.953 122.664 138.58 114.518 138.249 106.237C138.035 100.848 137.132 91.6179 137.333 85.3331C137.805 70.5801 137.049 57.7766 137.068 54.9579C137.226 31.2191 133.403 24.1826 128.055 24.1826C122.686 24.1826 118.758 31.7306 118.905 55.0184C118.921 57.6509 118.25 79.9999 118.465 85.4724C118.681 90.9704 117.772 101.738 117.586 106.677C117.261 115.307 115.963 122.664 115.305 122.664C114.484 122.664 112.606 116.254 112.374 107.422C112.227 101.854 112.681 94.6749 112.53 89.7504C112.215 79.5736 111.736 73.5926 111.56 64.8234C111.074 40.5279 107.472 38.2511 102.025 38.2511C97.5357 38.2511 93.9117 45.4194 93.8607 67.0086C93.8445 73.9341 93.2535 82.6651 93.782 90.5909C93.9832 93.6111 93.6265 101.219 93.3132 109.064C93.1135 114.066 93.0377 120.94 93.029 125.008C92.9865 144.657 86.351 158.364 83.8902 157.835C76.307 156.205 70.9782 146.856 66.0855 128.508C63.4477 118.617 56.9187 110.721 48.6105 109.055C42.1355 107.757 43.6647 120.595 48.2807 140.378C50.4432 149.645 55.258 161.935 58.1722 171.042C66.0855 195.771 89.5657 218.183 98.398 228.179C102.684 233.029 157.621 233.029 164.545 228.179C167.427 226.161 175.317 202.789 175.976 196.525Z" fill="#B2B2B2"/></g><defs><clipPath id="clip0_1451_461"><rect width="256" height="256" fill="white"/></clipPath></defs></svg> diff --git a/editor/icons/BoneMapHumanRightHand.svg b/editor/icons/BoneMapHumanRightHand.svg index 4e40af35d8..e92898152f 100644 --- a/editor/icons/BoneMapHumanRightHand.svg +++ b/editor/icons/BoneMapHumanRightHand.svg @@ -1 +1 @@ -<svg enable-background="new 0 0 1024 1024" height="1024" viewBox="0 0 1024 1024" width="1024" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h1024v1024h-1024z" fill="#3f3f3f"/><path d="m320.094 786.098c-7.046-66.929-28.135-153.363-18.529-260.192 1.143-12.71 4.5-48.282 4.46-82.732-.025-21.174 2.111-48.505 1.975-64.174-.167-19.333.428-41.584.625-55.755 1.052-75.44 13.225-85.827 30.813-85.827 17.246 0 26.77 14.266 27.062 84.582.061 14.42-.5 51-.5 58.5 0 17.508.333 34.167 0 53.5-.447 25.955 4.279 68 9 68 3.902 0 8.099-39.299 9.575-76.999.756-19.326 3.219-58.336 2.6-70.102-1.759-33.413-.474-58.914-1.537-90.165-3.183-93.607 13.016-111.729 34.695-111.729 21.973 0 35.979 57.688 34.849 114.224-.128 6.394 1.165 50.739-.188 89.859-.754 21.811 1.07 49.627 1.683 69.67 1.095 35.768 5.755 63.896 8.869 63.896 2.641 0 4.135-32.584 5.456-65.706.859-21.557 4.468-58.477 3.664-83.616-1.886-59.012 1.139-110.226 1.063-121.501-.635-94.955 14.66-123.101 36.052-123.101 21.476 0 37.188 30.192 36.6 123.343-.067 10.53 2.62 99.926 1.759 121.816-.865 21.992 2.773 65.062 3.517 84.818 1.299 34.521 6.49 63.947 9.124 63.947 3.281 0 10.794-25.638 11.724-60.965.587-22.275-1.231-50.99-.624-70.688 1.257-40.707 3.176-64.631 3.877-99.708 1.945-97.182 16.352-106.289 38.142-106.289 17.957 0 32.453 28.673 32.657 115.03.065 27.702 2.429 62.626.314 94.329-.805 12.081.622 42.512 1.875 73.894.799 20.007 1.102 47.501 1.137 63.775.171 78.595 26.713 133.424 36.556 131.308 30.333-6.521 51.648-43.918 71.219-117.307 10.551-39.566 36.667-71.149 69.9-77.813 25.9-5.193 19.783 46.161 1.318 125.293-8.649 37.068-27.909 86.227-39.566 122.655-31.652 98.917-125.573 188.563-160.902 228.546-17.146 19.403-236.894 19.403-264.59 0-11.525-8.07-43.087-101.557-45.724-126.616z" fill="#b2b2b2"/></svg> +<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_1451_464)"><path d="M0 0H256V256H0V0Z" fill="#3F3F3F"/><path d="M80.0233 196.525C78.2618 179.792 72.9896 158.184 75.3911 131.477C75.6768 128.299 76.5161 119.406 76.5061 110.794C76.4998 105.5 77.0338 98.6674 76.9998 94.7501C76.9581 89.9169 77.1068 84.3541 77.1561 80.8114C77.4191 61.9514 80.4623 59.3546 84.8593 59.3546C89.1708 59.3546 91.5518 62.9211 91.6248 80.5001C91.6401 84.1051 91.4998 93.2501 91.4998 95.1251C91.4998 99.5021 91.5831 103.667 91.4998 108.5C91.3881 114.989 92.5696 125.5 93.7498 125.5C94.7253 125.5 95.7746 115.675 96.1436 106.25C96.3326 101.419 96.9483 91.6664 96.7935 88.7249C96.3538 80.3716 96.6751 73.9964 96.4093 66.1836C95.6136 42.7819 99.6633 38.2514 105.083 38.2514C110.576 38.2514 114.078 52.6734 113.795 66.8074C113.763 68.4059 114.087 79.4921 113.748 89.2721C113.56 94.7249 114.016 101.679 114.169 106.69C114.443 115.632 115.608 122.664 116.386 122.664C117.047 122.664 117.42 114.518 117.75 106.237C117.965 100.848 118.867 91.6179 118.666 85.3331C118.195 70.5801 118.951 57.7766 118.932 54.9579C118.773 31.2191 122.597 24.1826 127.945 24.1826C133.314 24.1826 137.242 31.7306 137.095 55.0184C137.078 57.6509 137.75 79.9999 137.535 85.4724C137.319 90.9704 138.228 101.738 138.414 106.677C138.739 115.307 140.037 122.664 140.695 122.664C141.515 122.664 143.394 116.254 143.626 107.422C143.773 101.854 143.318 94.6749 143.47 89.7504C143.784 79.5736 144.264 73.5926 144.439 64.8234C144.926 40.5279 148.527 38.2511 153.975 38.2511C158.464 38.2511 162.088 45.4194 162.139 67.0086C162.155 73.9341 162.746 82.6651 162.218 90.5909C162.016 93.6111 162.373 101.219 162.686 109.064C162.886 114.066 162.962 120.94 162.971 125.008C163.013 144.657 169.649 158.364 172.11 157.835C179.693 156.205 185.022 146.856 189.914 128.508C192.552 118.617 199.081 110.721 207.389 109.055C213.864 107.757 212.335 120.595 207.719 140.378C205.557 149.645 200.742 161.935 197.827 171.042C189.914 195.771 166.434 218.183 157.602 228.179C153.315 233.029 98.3783 233.029 91.4543 228.179C88.5731 226.161 80.6826 202.789 80.0233 196.525Z" fill="#B2B2B2"/></g><defs><clipPath id="clip0_1451_464"><rect width="256" height="256" fill="white"/></clipPath></defs></svg> diff --git a/editor/icons/Keyboard.svg b/editor/icons/Keyboard.svg index b9dfab71ed..b6d963f9d7 100644 --- a/editor/icons/Keyboard.svg +++ b/editor/icons/Keyboard.svg @@ -1 +1 @@ -<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill-opacity=".996"><path d="m4 2a1 1 0 0 0 -1 1v9.084c0 .506.448.916 1 .916h8c.552 0 1-.41 1-.916v-9.084a1 1 0 0 0 -1-1zm1.543 1.139h1.393l1.834 4.199h1.295v.437c.708.052 1.246.239 1.61.559.368.316.55.747.55 1.295 0 .552-.182.99-.55 1.314-.368.32-.906.505-1.61.553v.467h-1.294v-.473c-.708-.06-1.247-.248-1.615-.564-.364-.316-.545-.75-.545-1.297 0-.548.181-.977.545-1.29.368-.315.907-.504 1.615-.564v-.437h-1.464l-.282-.733h-1.595l-.284.733h-1.439l1.836-4.2zm.684 1.39-.409 1.057h.817zm3.84 4.338v1.526c.28-.04.483-.12.607-.24.124-.125.185-.302.185-.53 0-.224-.063-.396-.191-.516-.124-.12-.326-.2-.602-.24zm-1.296.006c-.284.04-.487.12-.61.24-.12.116-.182.288-.182.516 0 .22.065.392.193.512.132.12.331.202.6.246v-1.514z" fill="#e0e0e0"/><path d="m27 2h7v14h-7z" fill="#fff"/><path d="m1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-9h-1v9a1 1 0 0 1 -1 1h-10a1 1 0 0 1 -1-1v-9z" fill="#e0e0e0"/></g></svg> +<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16"><path d="M6.584 5.135 6.17 4.059l-.412 1.076h.826zm3.203 2.976a1.032 1.032 0 0 0-.287.041.953.953 0 0 0-.09.034c-.028.012-.057.024-.084.039a.912.912 0 0 0-.152.107.988.988 0 0 0-.23.305.937.937 0 0 0-.04.097 1.017 1.017 0 0 0-.068.323 1.553 1.553 0 0 0 0 .244 1.374 1.374 0 0 0 .068.328 1.03 1.03 0 0 0 .201.336c.023.022.045.044.069.064a.96.96 0 0 0 .152.104c.027.015.056.027.084.039.03.012.06.024.09.033a.965.965 0 0 0 .19.035 1.028 1.028 0 0 0 .197 0 .974.974 0 0 0 .36-.107.876.876 0 0 0 .077-.049.872.872 0 0 0 .074-.055.882.882 0 0 0 .13-.136.978.978 0 0 0 .177-.368 1.225 1.225 0 0 0 .035-.224 1.61 1.61 0 0 0 0-.244 1.361 1.361 0 0 0-.035-.223 1.092 1.092 0 0 0-.121-.285.87.87 0 0 0-.12-.15.862.862 0 0 0-.14-.124c-.025-.017-.05-.035-.078-.05-.027-.015-.056-.027-.086-.04a.892.892 0 0 0-.373-.074z" style="fill-opacity:.99607843;fill:#e0e0e0"/><path d="M4 2c-.616-.02-1.084.59-1 1.178.003 3-.007 6 .005 9 .057.577.672.889 1.203.822 2.631-.003 5.263.006 7.894-.005a.973.973 0 0 0 .898-1.09c-.003-3.002.007-6.005-.005-9.007-.042-.592-.643-.976-1.203-.898H4Zm1.475.646H6.89l1.865 4.27H7.268l-.286-.744H5.36l-.287.744H3.61l1.866-4.27Zm4.312 4.301c1.069-.042 2.164.679 2.363 1.766.232 1.01-.34 2.144-1.326 2.502.296.465.837-.109 1.06-.007l.544.642c-.64.756-1.883.677-2.605.084-.394-.448-.866-.673-1.409-.887-1.175-.66-1.391-2.456-.43-3.39.463-.486 1.141-.716 1.803-.71Z" style="fill:#e0e0e0;fill-opacity:.996078"/><path fill="#e0e0e0" d="M1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4h-1v9a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4Z" style="fill-opacity:.996"/></svg> diff --git a/editor/icons/KeyboardError.svg b/editor/icons/KeyboardError.svg new file mode 100644 index 0000000000..e20d133155 --- /dev/null +++ b/editor/icons/KeyboardError.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16"><path d="M4 2c-.616-.02-1.084.59-1 1.178.003 3-.007 6 .005 9 .057.577.672.889 1.203.822 2.631-.003 5.263.006 7.894-.005a.973.973 0 0 0 .898-1.09c-.003-3.002.007-6.005-.005-9.007-.042-.592-.643-.976-1.203-.898H4Zm4.223 1.262c1.06.005 2.29.257 2.92 1.197.532.862.275 2.057-.484 2.703-.346.382-.862.629-1.075 1.117.055.345-.33.172-.537.213H7.148c-.037-.749.503-1.335 1.026-1.796.406-.253.744-1.002.129-1.22-.626-.25-1.374.117-1.645.715l-2.08-1.039c.599-1.147 1.868-1.818 3.136-1.872.17-.013.339-.018.509-.018Zm.127 5.697c.798-.057 1.616.616 1.54 1.45-.023.81-.841 1.413-1.623 1.328-.833.022-1.6-.771-1.443-1.613.097-.721.83-1.195 1.526-1.165z" style="fill:#ff5f5f;fill-opacity:1"/><path fill="#e0e0e0" d="M1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4h-1v9a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4Z" style="fill-opacity:1;fill:#ff5f5f"/></svg> diff --git a/editor/icons/KeyboardLabel.svg b/editor/icons/KeyboardLabel.svg new file mode 100644 index 0000000000..07a687f447 --- /dev/null +++ b/editor/icons/KeyboardLabel.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M11.814 1.977c-.078 0-.157.008-.232.023H4c-.7-.034-1.143.765-1 1.39.008 2.95-.017 5.9.014 8.848.13.705.965.847 1.562.76 2.542-.008 5.085.02 7.627-.014.734-.1.9-.952.797-1.564-.003-2.84.006-5.68-.006-8.522-.035-.594-.628-.927-1.18-.921zM9.797 4.016l.572.58-.572.578-.57-.578.57-.58zm-.606 1.05.594.606-.594.601-.591-.601.591-.606zm1.213 0 .596.606-.596.601-.593-.601.593-.606zm.717 1.436h1.06c.053.217.093.428.122.63.028.201.043.395.043.58a2.363 2.363 0 0 1-.133.724 1.425 1.425 0 0 1-.31.515c-.249.265-.598.399-1.05.399-.331 0-.594-.08-.785-.235a1.091 1.091 0 0 1-.236-.275c-.063.1-.14.19-.236.265-.206.163-.467.245-.787.245-.252 0-.457-.057-.614-.166a2.75 2.75 0 0 1-.095.42 1.936 1.936 0 0 1-.403.722c-.2.22-.452.383-.756.49-.303.11-.654.166-1.052.166-.466 0-.865-.089-1.2-.265a1.817 1.817 0 0 1-.765-.752c-.18-.327-.27-.715-.27-1.164 0-.256.027-.525.082-.809.055-.284.126-.545.21-.781h1.001c-.062.232-.112.46-.15.684a3.87 3.87 0 0 0-.053.613c0 .37.1.643.3.82.204.177.523.264.96.264.222 0 .425-.03.61-.092a.97.97 0 0 0 .439-.299.803.803 0 0 0 .166-.521 5.463 5.463 0 0 0-.051-.725 11.61 11.61 0 0 0-.068-.482 26.51 26.51 0 0 0-.096-.606h1.135l.043.276c.047.32.123.532.228.634.105.1.24.15.402.15.165 0 .284-.04.358-.124.076-.086.115-.224.115-.412v-.524h1.027v.524c0 .203.032.351.096.447.065.095.202.142.412.142a.637.637 0 0 0 .33-.078c.089-.052.133-.15.133-.297 0-.128-.019-.295-.054-.498l-.108-.605z" style="fill:#e0e0e0;fill-opacity:.996078"/><path fill="#e0e0e0" d="M1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4h-1v9a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4Z" style="fill-opacity:.996"/></svg> diff --git a/editor/icons/KeyboardPhysical.svg b/editor/icons/KeyboardPhysical.svg index 4364e0b4fa..7d4b7e2999 100644 --- a/editor/icons/KeyboardPhysical.svg +++ b/editor/icons/KeyboardPhysical.svg @@ -1 +1 @@ -<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill-opacity=".996"><path d="m4 2a1 1 0 0 0 -1 1v9.084c0 .506.448.916 1 .916h8c.552 0 1-.41 1-.916v-9.084a1 1 0 0 0 -1-1zm2.762 1.768h2.476l3.264 7.464h-2.604l-.502-1.3h-2.835l-.502 1.3h-2.561zm1.217 2.474-.725 1.878h1.45z" fill="#e0e0e0"/><path d="m27 2h7v14h-7z" fill="#fff"/><path d="m1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-9h-1v9a1 1 0 0 1 -1 1h-10a1 1 0 0 1 -1-1v-9z" fill="#e0e0e0"/></g></svg> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M4 2a1 1 0 0 0-1 1v9.084c0 .506.448.916 1 .916h8c.552 0 1-.41 1-.916V3a1 1 0 0 0-1-1Zm2.762 1.768h2.476l3.264 7.464H9.898l-.502-1.3H6.561l-.502 1.3H3.498Zm1.217 2.474L7.254 8.12h1.45z" style="fill-opacity:.996"/><path fill="#e0e0e0" d="M1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4h-1v9a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4Z" style="fill-opacity:.996"/></svg> diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp index 4f7d99c567..48e3759e57 100644 --- a/editor/input_event_configuration_dialog.cpp +++ b/editor/input_event_configuration_dialog.cpp @@ -38,9 +38,10 @@ #include "scene/gui/separator.h" #include "scene/gui/tree.h" -void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, bool p_update_input_list_selection) { +void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, const Ref<InputEvent> &p_original_event, bool p_update_input_list_selection) { if (p_event.is_valid()) { event = p_event; + original_event = p_original_event; // If the event is changed to something which is not the same as the listener, // clear out the event from the listener text box to avoid confusion. @@ -61,7 +62,7 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, b // Update option values and visibility bool show_mods = false; bool show_device = false; - bool show_phys_key = false; + bool show_key = false; if (mod.is_valid()) { show_mods = true; @@ -74,9 +75,25 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, b } if (k.is_valid()) { - show_phys_key = true; - physical_key_checkbox->set_pressed(k->get_physical_keycode() != Key::NONE && k->get_keycode() == Key::NONE); - + show_key = true; + if (k->get_keycode() == Key::NONE && k->get_physical_keycode() == Key::NONE && k->get_key_label() != Key::NONE) { + key_mode->select(KEYMODE_UNICODE); + } else if (k->get_keycode() != Key::NONE) { + key_mode->select(KEYMODE_KEYCODE); + } else if (k->get_physical_keycode() != Key::NONE) { + key_mode->select(KEYMODE_PHY_KEYCODE); + } else { + // Invalid key. + event = Ref<InputEvent>(); + original_event = Ref<InputEvent>(); + event_listener->clear_event(); + event_as_text->set_text(TTR("No Event Configured")); + + additional_options_container->hide(); + input_list_tree->deselect_all(); + _update_input_list(); + return; + } } else if (joyb.is_valid() || joym.is_valid() || mb.is_valid()) { show_device = true; _set_current_device(event->get_device()); @@ -84,11 +101,20 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, b mod_container->set_visible(show_mods); device_container->set_visible(show_device); - physical_key_checkbox->set_visible(show_phys_key); + key_mode->set_visible(show_key); additional_options_container->show(); + // Update mode selector based on original key event. + Ref<InputEventKey> ko = p_original_event; + if (ko.is_valid()) { + key_mode->set_item_disabled(KEYMODE_KEYCODE, ko->get_keycode() == Key::NONE); + key_mode->set_item_disabled(KEYMODE_PHY_KEYCODE, ko->get_physical_keycode() == Key::NONE); + key_mode->set_item_disabled(KEYMODE_UNICODE, ko->get_key_label() == Key::NONE); + } + // Update selected item in input list. if (p_update_input_list_selection && (k.is_valid() || joyb.is_valid() || joym.is_valid() || mb.is_valid())) { + in_tree_update = true; TreeItem *category = input_list_tree->get_root()->get_first_child(); while (category) { TreeItem *input_item = category->get_first_child(); @@ -97,6 +123,7 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, b // input_type should always be > 0, unless the tree structure has been misconfigured. int input_type = input_item->get_parent()->get_meta("__type", 0); if (input_type == 0) { + in_tree_update = false; return; } @@ -112,6 +139,7 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, b category->set_collapsed(false); input_item->select(0); input_list_tree->ensure_cursor_is_visible(); + in_tree_update = false; return; } input_item = input_item->get_next(); @@ -122,10 +150,12 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, b category->set_collapsed(true); // Event not in this category, so collapse; category = category->get_next(); } + in_tree_update = false; } } else { // Event is not valid, reset dialog - event = p_event; + event = Ref<InputEvent>(); + original_event = Ref<InputEvent>(); event_listener->clear_event(); event_as_text->set_text(TTR("No Event Configured")); @@ -141,8 +171,10 @@ void InputEventConfigurationDialog::_on_listen_input_changed(const Ref<InputEven return; } - // Create an editable reference + // Create an editable reference and a copy of full event. Ref<InputEvent> received_event = p_event; + Ref<InputEvent> received_original_event = received_event->duplicate(); + // Check what the type is and if it is allowed. Ref<InputEventKey> k = received_event; Ref<InputEventJoypadButton> joyb = received_event; @@ -169,12 +201,16 @@ void InputEventConfigurationDialog::_on_listen_input_changed(const Ref<InputEven } if (k.is_valid()) { - k->set_pressed(false); // To avoid serialization of 'pressed' property - doesn't matter for actions anyway. - // Maintain physical keycode option state - if (physical_key_checkbox->is_pressed()) { + k->set_pressed(false); // To avoid serialisation of 'pressed' property - doesn't matter for actions anyway. + if (key_mode->get_selected_id() == KEYMODE_KEYCODE) { + k->set_physical_keycode(Key::NONE); + k->set_key_label(Key::NONE); + } else if (key_mode->get_selected_id() == KEYMODE_PHY_KEYCODE) { k->set_keycode(Key::NONE); - } else { + k->set_key_label(Key::NONE); + } else if (key_mode->get_selected_id() == KEYMODE_UNICODE) { k->set_physical_keycode(Key::NONE); + k->set_keycode(Key::NONE); } } @@ -186,7 +222,7 @@ void InputEventConfigurationDialog::_on_listen_input_changed(const Ref<InputEven // Maintain device selection. received_event->set_device(_get_current_device()); - _set_event(received_event); + _set_event(received_event, received_original_event); } void InputEventConfigurationDialog::_on_listen_focus_changed() { @@ -326,14 +362,14 @@ void InputEventConfigurationDialog::_mod_toggled(bool p_checked, int p_index) { } } - _set_event(ie); + _set_event(ie, original_event); } void InputEventConfigurationDialog::_autoremap_command_or_control_toggled(bool p_checked) { Ref<InputEventWithModifiers> ie = event; if (ie.is_valid()) { ie->set_command_or_control_autoremap(p_checked); - _set_event(ie); + _set_event(ie, original_event); } if (p_checked) { @@ -345,27 +381,38 @@ void InputEventConfigurationDialog::_autoremap_command_or_control_toggled(bool p } } -void InputEventConfigurationDialog::_physical_keycode_toggled(bool p_checked) { +void InputEventConfigurationDialog::_key_mode_selected(int p_mode) { Ref<InputEventKey> k = event; - - if (k.is_null()) { + Ref<InputEventKey> ko = original_event; + if (k.is_null() || ko.is_null()) { return; } - if (p_checked) { - k->set_physical_keycode(k->get_keycode()); + if (key_mode->get_selected_id() == KEYMODE_KEYCODE) { + k->set_keycode(ko->get_keycode()); + k->set_physical_keycode(Key::NONE); + k->set_key_label(Key::NONE); + } else if (key_mode->get_selected_id() == KEYMODE_PHY_KEYCODE) { k->set_keycode(Key::NONE); - } else { - k->set_keycode((Key)k->get_physical_keycode()); + k->set_physical_keycode(ko->get_physical_keycode()); + k->set_key_label(Key::NONE); + } else if (key_mode->get_selected_id() == KEYMODE_UNICODE) { k->set_physical_keycode(Key::NONE); + k->set_keycode(Key::NONE); + k->set_key_label(ko->get_key_label()); } - _set_event(k); + _set_event(k, original_event); } void InputEventConfigurationDialog::_input_list_item_selected() { TreeItem *selected = input_list_tree->get_selected(); + // Called form _set_event, do not update for a second time. + if (in_tree_update) { + return; + } + // Invalid tree selection - type only exists on the "category" items, which are not a valid selection. if (selected->has_meta("__type")) { return; @@ -379,15 +426,11 @@ void InputEventConfigurationDialog::_input_list_item_selected() { Ref<InputEventKey> k; k.instantiate(); - if (physical_key_checkbox->is_pressed()) { - k->set_physical_keycode(keycode); - k->set_keycode(Key::NONE); - } else { - k->set_physical_keycode(Key::NONE); - k->set_keycode(keycode); - } + k->set_physical_keycode(keycode); + k->set_keycode(keycode); + k->set_key_label(keycode); - // Maintain modifier state from checkboxes + // Maintain modifier state from checkboxes. k->set_alt_pressed(mod_checkboxes[MOD_ALT]->is_pressed()); k->set_shift_pressed(mod_checkboxes[MOD_SHIFT]->is_pressed()); if (autoremap_command_or_control_checkbox->is_pressed()) { @@ -397,7 +440,23 @@ void InputEventConfigurationDialog::_input_list_item_selected() { k->set_meta_pressed(mod_checkboxes[MOD_META]->is_pressed()); } - _set_event(k, false); + Ref<InputEventKey> ko = k->duplicate(); + + if (key_mode->get_selected_id() == KEYMODE_UNICODE) { + key_mode->select(KEYMODE_PHY_KEYCODE); + } + + if (key_mode->get_selected_id() == KEYMODE_KEYCODE) { + k->set_physical_keycode(Key::NONE); + k->set_keycode(keycode); + k->set_key_label(Key::NONE); + } else if (key_mode->get_selected_id() == KEYMODE_PHY_KEYCODE) { + k->set_physical_keycode(keycode); + k->set_keycode(Key::NONE); + k->set_key_label(Key::NONE); + } + + _set_event(k, ko, false); } break; case INPUT_MOUSE_BUTTON: { MouseButton idx = (MouseButton)(int)selected->get_meta("__index"); @@ -417,7 +476,7 @@ void InputEventConfigurationDialog::_input_list_item_selected() { // Maintain selected device mb->set_device(_get_current_device()); - _set_event(mb, false); + _set_event(mb, mb, false); } break; case INPUT_JOY_BUTTON: { JoyButton idx = (JoyButton)(int)selected->get_meta("__index"); @@ -426,7 +485,7 @@ void InputEventConfigurationDialog::_input_list_item_selected() { // Maintain selected device jb->set_device(_get_current_device()); - _set_event(jb, false); + _set_event(jb, jb, false); } break; case INPUT_JOY_MOTION: { JoyAxis axis = (JoyAxis)(int)selected->get_meta("__axis"); @@ -440,7 +499,7 @@ void InputEventConfigurationDialog::_input_list_item_selected() { // Maintain selected device jm->set_device(_get_current_device()); - _set_event(jm, false); + _set_event(jm, jm, false); } break; } } @@ -470,7 +529,9 @@ void InputEventConfigurationDialog::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: { input_list_search->set_right_icon(input_list_search->get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); - physical_key_checkbox->set_icon(get_theme_icon(SNAME("KeyboardPhysical"), SNAME("EditorIcons"))); + key_mode->set_item_icon(KEYMODE_KEYCODE, get_theme_icon(SNAME("Keyboard"), SNAME("EditorIcons"))); + key_mode->set_item_icon(KEYMODE_PHY_KEYCODE, get_theme_icon(SNAME("KeyboardPhysical"), SNAME("EditorIcons"))); + key_mode->set_item_icon(KEYMODE_UNICODE, get_theme_icon(SNAME("KeyboardLabel"), SNAME("EditorIcons"))); icon_cache.keyboard = get_theme_icon(SNAME("Keyboard"), SNAME("EditorIcons")); icon_cache.mouse = get_theme_icon(SNAME("Mouse"), SNAME("EditorIcons")); @@ -484,22 +545,22 @@ void InputEventConfigurationDialog::_notification(int p_what) { void InputEventConfigurationDialog::popup_and_configure(const Ref<InputEvent> &p_event) { if (p_event.is_valid()) { - _set_event(p_event); + _set_event(p_event, p_event->duplicate()); } else { // Clear Event - _set_event(p_event); + _set_event(Ref<InputEvent>(), Ref<InputEvent>()); // Clear Checkbox Values for (int i = 0; i < MOD_MAX; i++) { mod_checkboxes[i]->set_pressed(false); } - // Enable the Physical Key checkbox by default to encourage its use. + // Enable the Physical Key by default to encourage its use. // Physical Key should be used for most game inputs as it allows keys to work // on non-QWERTY layouts out of the box. // This is especially important for WASD movement layouts. - physical_key_checkbox->set_pressed(true); + key_mode->select(KEYMODE_PHY_KEYCODE); autoremap_command_or_control_checkbox->set_pressed(false); // Select "All Devices" by default. @@ -621,14 +682,15 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() { mod_container->hide(); additional_options_container->add_child(mod_container); - // Physical Key Checkbox + // Key Mode Selection - physical_key_checkbox = memnew(CheckBox); - physical_key_checkbox->set_text(TTR("Use Physical Keycode")); - physical_key_checkbox->set_tooltip_text(TTR("Stores the physical position of the key on the keyboard rather than the key's value. Used for compatibility with non-latin layouts.\nThis should generally be enabled for most game shortcuts, but not in non-game applications.")); - physical_key_checkbox->connect("toggled", callable_mp(this, &InputEventConfigurationDialog::_physical_keycode_toggled)); - physical_key_checkbox->hide(); - additional_options_container->add_child(physical_key_checkbox); + key_mode = memnew(OptionButton); + key_mode->add_item("Keycode (Latin equvialent)", KEYMODE_KEYCODE); + key_mode->add_item("Physical Keycode (poistion of US QWERTY keyboard)", KEYMODE_PHY_KEYCODE); + key_mode->add_item("Unicode (case-insencetive)", KEYMODE_UNICODE); + key_mode->connect("item_selected", callable_mp(this, &InputEventConfigurationDialog::_key_mode_selected)); + key_mode->hide(); + additional_options_container->add_child(key_mode); main_vbox->add_child(additional_options_container); } diff --git a/editor/input_event_configuration_dialog.h b/editor/input_event_configuration_dialog.h index 67906233dd..e7ab0da4d6 100644 --- a/editor/input_event_configuration_dialog.h +++ b/editor/input_event_configuration_dialog.h @@ -50,7 +50,10 @@ private: Ref<Texture2D> joypad_axis; } icon_cache; - Ref<InputEvent> event = Ref<InputEvent>(); + Ref<InputEvent> event; + Ref<InputEvent> original_event; + + bool in_tree_update = false; // Listening for input EventListenerLineEdit *event_listener = nullptr; @@ -88,9 +91,15 @@ private: CheckBox *mod_checkboxes[MOD_MAX]; CheckBox *autoremap_command_or_control_checkbox = nullptr; - CheckBox *physical_key_checkbox = nullptr; + enum KeyMode { + KEYMODE_KEYCODE, + KEYMODE_PHY_KEYCODE, + KEYMODE_UNICODE, + }; + + OptionButton *key_mode = nullptr; - void _set_event(const Ref<InputEvent> &p_event, bool p_update_input_list_selection = true); + void _set_event(const Ref<InputEvent> &p_event, const Ref<InputEvent> &p_original_event, bool p_update_input_list_selection = true); void _on_listen_input_changed(const Ref<InputEvent> &p_event); void _on_listen_focus_changed(); @@ -100,7 +109,7 @@ private: void _mod_toggled(bool p_checked, int p_index); void _autoremap_command_or_control_toggled(bool p_checked); - void _physical_keycode_toggled(bool p_checked); + void _key_mode_selected(int p_mode); void _device_selection_changed(int p_option_button_index); void _set_current_device(int p_device); diff --git a/editor/plugins/dedicated_server_export_plugin.cpp b/editor/plugins/dedicated_server_export_plugin.cpp new file mode 100644 index 0000000000..013706034e --- /dev/null +++ b/editor/plugins/dedicated_server_export_plugin.cpp @@ -0,0 +1,139 @@ +/**************************************************************************/ +/* dedicated_server_export_plugin.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 "dedicated_server_export_plugin.h" + +EditorExportPreset::FileExportMode DedicatedServerExportPlugin::_get_export_mode_for_path(const String &p_path) { + Ref<EditorExportPreset> preset = get_export_preset(); + ERR_FAIL_COND_V(preset.is_null(), EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED); + + EditorExportPreset::FileExportMode mode = preset->get_file_export_mode(p_path); + if (mode != EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED) { + return mode; + } + + String path = p_path; + if (path.begins_with("res://")) { + path = path.substr(6); + } + + Vector<String> parts = path.split("/"); + + while (parts.size() > 0) { + parts.resize(parts.size() - 1); + + String test_path = "res://"; + if (parts.size() > 0) { + test_path += String("/").join(parts) + "/"; + } + + mode = preset->get_file_export_mode(test_path); + if (mode != EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED) { + break; + } + } + + return mode; +} + +PackedStringArray DedicatedServerExportPlugin::_get_export_features(const Ref<EditorExportPlatform> &p_platform, bool p_debug) const { + PackedStringArray ret; + + Ref<EditorExportPreset> preset = get_export_preset(); + ERR_FAIL_COND_V(preset.is_null(), ret); + + if (preset->is_dedicated_server()) { + ret.append("dedicated_server"); + } + return ret; +} + +uint64_t DedicatedServerExportPlugin::_get_customization_configuration_hash() const { + Ref<EditorExportPreset> preset = get_export_preset(); + ERR_FAIL_COND_V(preset.is_null(), 0); + + if (preset->get_export_filter() != EditorExportPreset::EXPORT_CUSTOMIZED) { + return 0; + } + + return preset->get_customized_files().hash(); +} + +bool DedicatedServerExportPlugin::_begin_customize_scenes(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) { + Ref<EditorExportPreset> preset = get_export_preset(); + ERR_FAIL_COND_V(preset.is_null(), false); + + current_export_mode = EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED; + + return preset->get_export_filter() == EditorExportPreset::EXPORT_CUSTOMIZED; +} + +bool DedicatedServerExportPlugin::_begin_customize_resources(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) { + Ref<EditorExportPreset> preset = get_export_preset(); + ERR_FAIL_COND_V(preset.is_null(), false); + + current_export_mode = EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED; + + return preset->get_export_filter() == EditorExportPreset::EXPORT_CUSTOMIZED; +} + +Node *DedicatedServerExportPlugin::_customize_scene(Node *p_root, const String &p_path) { + // Simply set the export mode based on the scene path. All the real + // customization happens in _customize_resource(). + current_export_mode = _get_export_mode_for_path(p_path); + return nullptr; +} + +Ref<Resource> DedicatedServerExportPlugin::_customize_resource(const Ref<Resource> &p_resource, const String &p_path) { + // If the resource has a path, we use that to get our export mode. But if it + // doesn't, we assume that this resource is embedded in the last resource with + // a path. + if (p_path != "") { + current_export_mode = _get_export_mode_for_path(p_path); + } + + if (p_resource.is_valid() && current_export_mode == EditorExportPreset::MODE_FILE_STRIP && p_resource->has_method("create_placeholder")) { + Callable::CallError err; + Ref<Resource> result = const_cast<Resource *>(p_resource.ptr())->callp("create_placeholder", nullptr, 0, err); + if (err.error == Callable::CallError::CALL_OK) { + return result; + } + } + + return Ref<Resource>(); +} + +void DedicatedServerExportPlugin::_end_customize_scenes() { + current_export_mode = EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED; +} + +void DedicatedServerExportPlugin::_end_customize_resources() { + current_export_mode = EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED; +} diff --git a/editor/plugins/dedicated_server_export_plugin.h b/editor/plugins/dedicated_server_export_plugin.h new file mode 100644 index 0000000000..cb014ae52d --- /dev/null +++ b/editor/plugins/dedicated_server_export_plugin.h @@ -0,0 +1,58 @@ +/**************************************************************************/ +/* dedicated_server_export_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. */ +/**************************************************************************/ + +#ifndef DEDICATED_SERVER_EXPORT_PLUGIN_H +#define DEDICATED_SERVER_EXPORT_PLUGIN_H + +#include "editor/export/editor_export.h" + +class DedicatedServerExportPlugin : public EditorExportPlugin { +private: + EditorExportPreset::FileExportMode current_export_mode; + + EditorExportPreset::FileExportMode _get_export_mode_for_path(const String &p_path); + +protected: + String _get_name() const override { return "DedicatedServer"; } + + PackedStringArray _get_export_features(const Ref<EditorExportPlatform> &p_platform, bool p_debug) const override; + uint64_t _get_customization_configuration_hash() const override; + + bool _begin_customize_scenes(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) override; + bool _begin_customize_resources(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) override; + + Node *_customize_scene(Node *p_root, const String &p_path) override; + Ref<Resource> _customize_resource(const Ref<Resource> &p_resource, const String &p_path) override; + + void _end_customize_scenes() override; + void _end_customize_resources() override; +}; + +#endif // DEDICATED_SERVER_EXPORT_PLUGIN_H diff --git a/editor/plugins/lightmap_gi_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp index 1adcc2a3b4..519cfcaa94 100644 --- a/editor/plugins/lightmap_gi_editor_plugin.cpp +++ b/editor/plugins/lightmap_gi_editor_plugin.cpp @@ -35,12 +35,43 @@ void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) { if (lightmap) { - LightmapGI::BakeError err; + LightmapGI::BakeError err = LightmapGI::BAKE_ERROR_OK; const uint64_t time_started = OS::get_singleton()->get_ticks_msec(); - if (get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root() == lightmap) { - err = lightmap->bake(lightmap, p_file, bake_func_step); + if (get_tree()->get_edited_scene_root()) { + Ref<LightmapGIData> lightmapGIData = lightmap->get_light_data(); + + if (lightmapGIData.is_valid()) { + String path = lightmapGIData->get_path(); + if (!path.is_resource_file()) { + int srpos = path.find("::"); + if (srpos != -1) { + String base = path.substr(0, srpos); + if (ResourceLoader::get_resource_type(base) == "PackedScene") { + if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) { + err = LightmapGI::BAKE_ERROR_FOREIGN_DATA; + } + } else { + if (FileAccess::exists(base + ".import")) { + err = LightmapGI::BAKE_ERROR_FOREIGN_DATA; + } + } + } + } else { + if (FileAccess::exists(path + ".import")) { + err = LightmapGI::BAKE_ERROR_FOREIGN_DATA; + } + } + } + + if (err == LightmapGI::BAKE_ERROR_OK) { + if (get_tree()->get_edited_scene_root() == lightmap) { + err = lightmap->bake(lightmap, p_file, bake_func_step); + } else { + err = lightmap->bake(lightmap->get_parent(), p_file, bake_func_step); + } + } } else { - err = lightmap->bake(lightmap->get_parent(), p_file, bake_func_step); + err = LightmapGI::BAKE_ERROR_NO_SCENE_ROOT; } bake_func_end(time_started); @@ -59,16 +90,21 @@ void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) { file_dialog->set_current_path(scene_path); file_dialog->popup_file_dialog(); - } break; - case LightmapGI::BAKE_ERROR_NO_MESHES: + case LightmapGI::BAKE_ERROR_NO_MESHES: { EditorNode::get_singleton()->show_warning(TTR("No meshes to bake. Make sure they contain an UV2 channel and that the 'Bake Light' flag is on.")); - break; - case LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE: + } break; + case LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE: { EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images, make sure path is writable.")); - break; + } break; + case LightmapGI::BAKE_ERROR_NO_SCENE_ROOT: { + EditorNode::get_singleton()->show_warning(TTR("No editor scene root found.")); + } break; + case LightmapGI::BAKE_ERROR_FOREIGN_DATA: { + EditorNode::get_singleton()->show_warning(TTR("Lightmap data is not local to the scene.")); + } break; default: { - } + } break; } } } diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 3ee9823f3a..782e365138 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -266,15 +266,15 @@ void Skeleton3DEditor::reset_pose(const bool p_all_bones) { if (!skeleton) { return; } - const int bone_len = skeleton->get_bone_count(); - if (!bone_len) { + const int bone_count = skeleton->get_bone_count(); + if (!bone_count) { return; } EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); if (p_all_bones) { - for (int i = 0; i < bone_len; i++) { + for (int i = 0; i < bone_count; i++) { ur->add_undo_method(skeleton, "set_bone_pose_position", i, skeleton->get_bone_pose_position(i)); ur->add_undo_method(skeleton, "set_bone_pose_rotation", i, skeleton->get_bone_pose_rotation(i)); ur->add_undo_method(skeleton, "set_bone_pose_scale", i, skeleton->get_bone_pose_scale(i)); @@ -333,15 +333,15 @@ void Skeleton3DEditor::pose_to_rest(const bool p_all_bones) { if (!skeleton) { return; } - const int bone_len = skeleton->get_bone_count(); - if (!bone_len) { + const int bone_count = skeleton->get_bone_count(); + if (!bone_count) { return; } EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Set Bone Rest"), UndoRedo::MERGE_ENDS); if (p_all_bones) { - for (int i = 0; i < bone_len; i++) { + for (int i = 0; i < bone_count; i++) { ur->add_do_method(skeleton, "set_bone_rest", i, skeleton->get_bone_pose(i)); ur->add_undo_method(skeleton, "set_bone_rest", i, skeleton->get_bone_rest(i)); } @@ -362,57 +362,56 @@ void Skeleton3DEditor::create_physical_skeleton() { ERR_FAIL_COND(!get_tree()); Node *owner = get_tree()->get_edited_scene_root(); - const int bc = skeleton->get_bone_count(); + const int bone_count = skeleton->get_bone_count(); - if (!bc) { + if (!bone_count) { + EditorNode::get_singleton()->show_warning(vformat(TTR("Cannot create a physical skeleton for a Skeleton3D node with no bones."))); return; } Vector<BoneInfo> bones_infos; - bones_infos.resize(bc); + bones_infos.resize(bone_count); - if (bc > 0) { - ur->create_action(TTR("Create physical bones"), UndoRedo::MERGE_ALL); - for (int bone_id = 0; bc > bone_id; ++bone_id) { - const int parent = skeleton->get_bone_parent(bone_id); + ur->create_action(TTR("Create physical bones"), UndoRedo::MERGE_ALL); + for (int bone_id = 0; bone_count > bone_id; ++bone_id) { + const int parent = skeleton->get_bone_parent(bone_id); - if (parent < 0) { - bones_infos.write[bone_id].relative_rest = skeleton->get_bone_rest(bone_id); - } else { - const int parent_parent = skeleton->get_bone_parent(parent); - - bones_infos.write[bone_id].relative_rest = bones_infos[parent].relative_rest * skeleton->get_bone_rest(bone_id); - - // Create physical bone on parent. - if (!bones_infos[parent].physical_bone) { - PhysicalBone3D *physical_bone = create_physical_bone(parent, bone_id, bones_infos); - if (physical_bone && physical_bone->get_child(0)) { - CollisionShape3D *collision_shape = Object::cast_to<CollisionShape3D>(physical_bone->get_child(0)); - if (collision_shape) { - bones_infos.write[parent].physical_bone = physical_bone; - - ur->add_do_method(skeleton, "add_child", physical_bone); - ur->add_do_method(physical_bone, "set_owner", owner); - ur->add_do_method(collision_shape, "set_owner", owner); - ur->add_do_property(physical_bone, "bone_name", skeleton->get_bone_name(parent)); - - // Create joint between parent of parent. - if (parent_parent != -1) { - ur->add_do_method(physical_bone, "set_joint_type", PhysicalBone3D::JOINT_TYPE_PIN); - } + if (parent < 0) { + bones_infos.write[bone_id].relative_rest = skeleton->get_bone_rest(bone_id); + } else { + const int parent_parent = skeleton->get_bone_parent(parent); + + bones_infos.write[bone_id].relative_rest = bones_infos[parent].relative_rest * skeleton->get_bone_rest(bone_id); + + // Create physical bone on parent. + if (!bones_infos[parent].physical_bone) { + PhysicalBone3D *physical_bone = create_physical_bone(parent, bone_id, bones_infos); + if (physical_bone && physical_bone->get_child(0)) { + CollisionShape3D *collision_shape = Object::cast_to<CollisionShape3D>(physical_bone->get_child(0)); + if (collision_shape) { + bones_infos.write[parent].physical_bone = physical_bone; + + ur->add_do_method(skeleton, "add_child", physical_bone); + ur->add_do_method(physical_bone, "set_owner", owner); + ur->add_do_method(collision_shape, "set_owner", owner); + ur->add_do_property(physical_bone, "bone_name", skeleton->get_bone_name(parent)); + + // Create joint between parent of parent. + if (parent_parent != -1) { + ur->add_do_method(physical_bone, "set_joint_type", PhysicalBone3D::JOINT_TYPE_PIN); + } - ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, physical_bone); - ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, collision_shape); + ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, physical_bone); + ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, collision_shape); - ur->add_do_reference(physical_bone); - ur->add_undo_method(skeleton, "remove_child", physical_bone); - } + ur->add_do_reference(physical_bone); + ur->add_undo_method(skeleton, "remove_child", physical_bone); } } } } - ur->commit_action(); } + ur->commit_action(); } PhysicalBone3D *Skeleton3DEditor::create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos) { @@ -457,6 +456,11 @@ PhysicalBone3D *Skeleton3DEditor::create_physical_bone(int bone_id, int bone_chi } void Skeleton3DEditor::export_skeleton_profile() { + if (!skeleton->get_bone_count()) { + EditorNode::get_singleton()->show_warning(vformat(TTR("Cannot export a SkeletonProfile for a Skeleton3D node with no bones."))); + return; + } + file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); file_dialog->set_title(TTR("Export Skeleton Profile As...")); @@ -481,9 +485,9 @@ void Skeleton3DEditor::_file_selected(const String &p_file) { Vector2 position_max; Vector2 position_min; - int len = skeleton->get_bone_count(); - sp->set_bone_size(len); - for (int i = 0; i < len; i++) { + const int bone_count = skeleton->get_bone_count(); + sp->set_bone_size(bone_count); + for (int i = 0; i < bone_count; i++) { sp->set_bone_name(i, skeleton->get_bone_name(i)); int parent = skeleton->get_bone_parent(i); if (parent >= 0) { @@ -509,7 +513,7 @@ void Skeleton3DEditor::_file_selected(const String &p_file) { Vector2 center = Vector2((position_max.x + position_min.x) * 0.5, (position_max.y + position_min.y) * 0.5); float nrm = MAX(bound.x, bound.y); if (nrm > 0) { - for (int i = 0; i < len; i++) { + for (int i = 0; i < bone_count; i++) { handle_positions.write[i] = (handle_positions[i] - center) / nrm * 0.9; sp->set_handle_offset(i, Vector2(0.5 + handle_positions[i].x, 0.5 - handle_positions[i].y)); } @@ -980,25 +984,31 @@ void Skeleton3DEditor::_draw_gizmo() { } void Skeleton3DEditor::_draw_handles() { - handles_mesh_instance->show(); + const int bone_count = skeleton->get_bone_count(); - const int bone_len = skeleton->get_bone_count(); handles_mesh->clear_surfaces(); - handles_mesh->surface_begin(Mesh::PRIMITIVE_POINTS); - for (int i = 0; i < bone_len; i++) { - Color c; - if (i == selected_bone) { - c = Color(1, 1, 0); - } else { - c = Color(0.1, 0.25, 0.8); + if (bone_count) { + handles_mesh_instance->show(); + + handles_mesh->surface_begin(Mesh::PRIMITIVE_POINTS); + + for (int i = 0; i < bone_count; i++) { + Color c; + if (i == selected_bone) { + c = Color(1, 1, 0); + } else { + c = Color(0.1, 0.25, 0.8); + } + Vector3 point = skeleton->get_bone_global_pose(i).origin; + handles_mesh->surface_set_color(c); + handles_mesh->surface_add_vertex(point); } - Vector3 point = skeleton->get_bone_global_pose(i).origin; - handles_mesh->surface_set_color(c); - handles_mesh->surface_add_vertex(point); + handles_mesh->surface_end(); + handles_mesh->surface_set_material(0, handle_material); + } else { + handles_mesh_instance->hide(); } - handles_mesh->surface_end(); - handles_mesh->surface_set_material(0, handle_material); } TreeItem *Skeleton3DEditor::_find(TreeItem *p_node, const NodePath &p_path) { @@ -1253,8 +1263,8 @@ int Skeleton3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gi Transform3D gt = skeleton->get_global_transform(); int closest_idx = -1; real_t closest_dist = 1e10; - const int bone_len = skeleton->get_bone_count(); - for (int i = 0; i < bone_len; i++) { + const int bone_count = skeleton->get_bone_count(); + for (int i = 0; i < bone_count; i++) { Vector3 joint_pos_3d = gt.xform(skeleton->get_bone_global_pose(i).origin); Vector2 joint_pos_2d = p_camera->unproject_position(joint_pos_3d); real_t dist_3d = ray_from.distance_to(joint_pos_3d); @@ -1349,6 +1359,10 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); + if (!skeleton->get_bone_count()) { + return; + } + int selected = -1; Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); if (se) { diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 4c5cde926a..af70e64b6a 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -40,6 +40,7 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.h" +#include "editor/filesystem_dock.h" #include "editor/inspector_dock.h" #include "editor/plugins/curve_editor_plugin.h" #include "editor/plugins/shader_editor_plugin.h" @@ -1271,18 +1272,55 @@ Dictionary VisualShaderEditor::get_custom_node_data(Ref<VisualShaderNodeCustom> return dict; } -void VisualShaderEditor::update_custom_type(const Ref<Resource> &p_resource) { - Ref<Script> scr = Ref<Script>(p_resource.ptr()); - if (scr.is_null() || scr->get_instance_base_type() != "VisualShaderNodeCustom") { +void VisualShaderEditor::_get_current_mode_limits(int &r_begin_type, int &r_end_type) const { + switch (visual_shader->get_mode()) { + case Shader::MODE_CANVAS_ITEM: + case Shader::MODE_SPATIAL: { + r_begin_type = 0; + r_end_type = 3; + } break; + case Shader::MODE_PARTICLES: { + r_begin_type = 3; + r_end_type = 5 + r_begin_type; + } break; + case Shader::MODE_SKY: { + r_begin_type = 8; + r_end_type = 1 + r_begin_type; + } break; + case Shader::MODE_FOG: { + r_begin_type = 9; + r_end_type = 1 + r_begin_type; + } break; + default: { + } break; + } +} + +void VisualShaderEditor::_script_created(const Ref<Script> &p_script) { + if (p_script.is_null() || p_script->get_instance_base_type() != "VisualShaderNodeCustom") { + return; + } + Ref<VisualShaderNodeCustom> ref; + ref.instantiate(); + ref->set_script(p_script); + + Dictionary dict = get_custom_node_data(ref); + add_custom_type(dict["name"], dict["script"], dict["description"], dict["return_icon_type"], dict["category"], dict["highend"]); + + _update_options_menu(); +} + +void VisualShaderEditor::_update_custom_script(const Ref<Script> &p_script) { + if (p_script.is_null() || p_script->get_instance_base_type() != "VisualShaderNodeCustom") { return; } Ref<VisualShaderNodeCustom> ref; ref.instantiate(); - ref->set_script(scr); + ref->set_script(p_script); if (!ref->is_available(visual_shader->get_mode(), visual_shader->get_shader_type())) { for (int i = 0; i < add_options.size(); i++) { - if (add_options[i].is_custom && add_options[i].script == scr) { + if (add_options[i].is_custom && add_options[i].script == p_script) { add_options.remove_at(i); _update_options_menu(); // TODO: Make indication for the existed custom nodes with that script on graph to be disabled. @@ -1296,8 +1334,8 @@ void VisualShaderEditor::update_custom_type(const Ref<Resource> &p_resource) { bool found_type = false; bool need_rebuild = false; - for (int i = 0; i < add_options.size(); i++) { - if (add_options[i].is_custom && add_options[i].script == scr) { + for (int i = custom_node_option_idx; i < add_options.size(); i++) { + if (add_options[i].script == p_script) { found_type = true; add_options.write[i].name = dict["name"]; @@ -1306,31 +1344,11 @@ void VisualShaderEditor::update_custom_type(const Ref<Resource> &p_resource) { add_options.write[i].category = dict["category"]; add_options.write[i].highend = dict["highend"]; - int max_type = 0; - int type_offset = 0; - switch (visual_shader->get_mode()) { - case Shader::MODE_CANVAS_ITEM: - case Shader::MODE_SPATIAL: { - max_type = 3; - } break; - case Shader::MODE_PARTICLES: { - max_type = 5; - type_offset = 3; - } break; - case Shader::MODE_SKY: { - max_type = 1; - type_offset = 8; - } break; - case Shader::MODE_FOG: { - max_type = 1; - type_offset = 9; - } break; - default: { - } break; - } - max_type = type_offset + max_type; + int begin_type = 0; + int end_type = 0; + _get_current_mode_limits(begin_type, end_type); - for (int t = type_offset; t < max_type; t++) { + for (int t = begin_type; t < end_type; t++) { VisualShader::Type type = (VisualShader::Type)t; Vector<int> nodes = visual_shader->get_node_list(type); @@ -1339,28 +1357,27 @@ void VisualShaderEditor::update_custom_type(const Ref<Resource> &p_resource) { List<VisualShader::Connection> custom_node_input_connections; List<VisualShader::Connection> custom_node_output_connections; + for (const VisualShader::Connection &E : node_connections) { int from = E.from_node; - int from_idx = E.from_port; + int from_port = E.from_port; int to = E.to_node; - int to_idx = E.to_port; + int to_port = E.to_port; - if (graph_plugin->get_node_script(from) == scr) { - custom_node_output_connections.push_back({ from, from_idx, to, to_idx }); - } else if (graph_plugin->get_node_script(to) == scr) { - custom_node_input_connections.push_back({ from, from_idx, to, to_idx }); + if (graph_plugin->get_node_script(from) == p_script) { + custom_node_output_connections.push_back({ from, from_port, to, to_port }); + } else if (graph_plugin->get_node_script(to) == p_script) { + custom_node_input_connections.push_back({ from, from_port, to, to_port }); } } - for (int j = 0; j < nodes.size(); j++) { - int node_id = nodes[j]; - + for (int node_id : nodes) { Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, node_id); if (vsnode.is_null()) { continue; } Ref<VisualShaderNodeCustom> custom_node = Ref<VisualShaderNodeCustom>(vsnode.ptr()); - if (custom_node.is_null() || custom_node->get_script() != scr) { + if (custom_node.is_null() || custom_node->get_script() != p_script) { continue; } need_rebuild = true; @@ -1429,6 +1446,89 @@ void VisualShaderEditor::update_custom_type(const Ref<Resource> &p_resource) { } } +void VisualShaderEditor::_resource_saved(const Ref<Resource> &p_resource) { + _update_custom_script(Ref<Script>(p_resource.ptr())); +} + +void VisualShaderEditor::_resources_removed() { + bool has_any_instance = false; + + for (const Ref<Script> &scr : custom_scripts_to_delete) { + for (int i = custom_node_option_idx; i < add_options.size(); i++) { + if (add_options[i].script == scr) { + add_options.remove_at(i); + + // Removes all node instances using that script from the graph. + { + int begin_type = 0; + int end_type = 0; + _get_current_mode_limits(begin_type, end_type); + + for (int t = begin_type; t < end_type; t++) { + VisualShader::Type type = (VisualShader::Type)t; + + List<VisualShader::Connection> node_connections; + visual_shader->get_node_connections(type, &node_connections); + + for (const VisualShader::Connection &E : node_connections) { + int from = E.from_node; + int from_port = E.from_port; + int to = E.to_node; + int to_port = E.to_port; + + if (graph_plugin->get_node_script(from) == scr || graph_plugin->get_node_script(to) == scr) { + visual_shader->disconnect_nodes(type, from, from_port, to, to_port); + graph_plugin->disconnect_nodes(type, from, from_port, to, to_port); + } + } + + Vector<int> nodes = visual_shader->get_node_list(type); + for (int node_id : nodes) { + Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, node_id); + if (vsnode.is_null()) { + continue; + } + Ref<VisualShaderNodeCustom> custom_node = Ref<VisualShaderNodeCustom>(vsnode.ptr()); + if (custom_node.is_null() || custom_node->get_script() != scr) { + continue; + } + visual_shader->remove_node(type, node_id); + graph_plugin->remove_node(type, node_id, false); + + has_any_instance = true; + } + } + } + + break; + } + } + } + if (has_any_instance) { + EditorUndoRedoManager::get_singleton()->clear_history(); // Need to clear undo history, otherwise it may lead to hard-detected errors and crashes (since the script was removed). + ResourceSaver::save(visual_shader, visual_shader->get_path()); + } + _update_options_menu(); + + custom_scripts_to_delete.clear(); + pending_custom_scripts_to_delete = false; +} + +void VisualShaderEditor::_resource_removed(const Ref<Resource> &p_resource) { + Ref<Script> scr = Ref<Script>(p_resource.ptr()); + if (scr.is_null() || scr->get_instance_base_type() != "VisualShaderNodeCustom") { + return; + } + + custom_scripts_to_delete.push_back(scr); + + if (!pending_custom_scripts_to_delete) { + pending_custom_scripts_to_delete = true; + + call_deferred("_resources_removed"); + } +} + void VisualShaderEditor::_update_options_menu_deferred() { _update_options_menu(); @@ -4901,13 +5001,16 @@ void VisualShaderEditor::_bind_methods() { ClassDB::bind_method("_expand_output_port", &VisualShaderEditor::_expand_output_port); ClassDB::bind_method("_update_options_menu_deferred", &VisualShaderEditor::_update_options_menu_deferred); ClassDB::bind_method("_rebuild_shader_deferred", &VisualShaderEditor::_rebuild_shader_deferred); + ClassDB::bind_method("_resources_removed", &VisualShaderEditor::_resources_removed); ClassDB::bind_method("_is_available", &VisualShaderEditor::_is_available); } VisualShaderEditor::VisualShaderEditor() { ShaderLanguage::get_keyword_list(&keyword_list); - EditorNode::get_singleton()->connect("resource_saved", callable_mp(this, &VisualShaderEditor::update_custom_type)); + EditorNode::get_singleton()->connect("resource_saved", callable_mp(this, &VisualShaderEditor::_resource_saved)); + FileSystemDock::get_singleton()->get_script_create_dialog()->connect("script_created", callable_mp(this, &VisualShaderEditor::_script_created)); + FileSystemDock::get_singleton()->connect("resource_removed", callable_mp(this, &VisualShaderEditor::_resource_removed)); graph = memnew(GraphEdit); graph->get_zoom_hbox()->set_h_size_flags(SIZE_EXPAND_FILL); diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h index c4f6b4952c..519a390ccc 100644 --- a/editor/plugins/visual_shader_editor_plugin.h +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -188,6 +188,9 @@ class VisualShaderEditor : public VBoxContainer { PanelContainer *error_panel = nullptr; Label *error_label = nullptr; + bool pending_custom_scripts_to_delete = false; + List<Ref<Script>> custom_scripts_to_delete; + bool _block_update_options_menu = false; bool _block_rebuild_shader = false; @@ -503,6 +506,13 @@ class VisualShaderEditor : public VBoxContainer { void _visibility_changed(); + void _get_current_mode_limits(int &r_begin_type, int &r_end_type) const; + void _update_custom_script(const Ref<Script> &p_script); + void _script_created(const Ref<Script> &p_script); + void _resource_saved(const Ref<Resource> &p_resource); + void _resource_removed(const Ref<Resource> &p_resource); + void _resources_removed(); + protected: void _notification(int p_what); static void _bind_methods(); @@ -517,7 +527,6 @@ public: void add_custom_type(const String &p_name, const Ref<Script> &p_script, const String &p_description, int p_return_icon_type, const String &p_category, bool p_highend); Dictionary get_custom_node_data(Ref<VisualShaderNodeCustom> &p_custom_node); - void update_custom_type(const Ref<Resource> &p_resource); virtual Size2 get_minimum_size() const override; void edit(VisualShader *p_visual_shader); diff --git a/editor/plugins/voxel_gi_editor_plugin.cpp b/editor/plugins/voxel_gi_editor_plugin.cpp index a3ccf392e6..f9f72fee77 100644 --- a/editor/plugins/voxel_gi_editor_plugin.cpp +++ b/editor/plugins/voxel_gi_editor_plugin.cpp @@ -35,7 +35,9 @@ void VoxelGIEditorPlugin::_bake() { if (voxel_gi) { - if (voxel_gi->get_probe_data().is_null()) { + Ref<VoxelGIData> voxel_gi_data = voxel_gi->get_probe_data(); + + if (voxel_gi_data.is_null()) { String path = get_tree()->get_edited_scene_root()->get_scene_file_path(); if (path.is_empty()) { path = "res://" + voxel_gi->get_name() + "_data.res"; @@ -46,7 +48,32 @@ void VoxelGIEditorPlugin::_bake() { probe_file->set_current_path(path); probe_file->popup_file_dialog(); return; + } else { + String path = voxel_gi_data->get_path(); + if (!path.is_resource_file()) { + int srpos = path.find("::"); + if (srpos != -1) { + String base = path.substr(0, srpos); + if (ResourceLoader::get_resource_type(base) == "PackedScene") { + if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) { + EditorNode::get_singleton()->show_warning(TTR("Voxel GI data is not local to the scene.")); + return; + } + } else { + if (FileAccess::exists(base + ".import")) { + EditorNode::get_singleton()->show_warning(TTR("Voxel GI data is part of an imported resource.")); + return; + } + } + } + } else { + if (FileAccess::exists(path + ".import")) { + EditorNode::get_singleton()->show_warning(TTR("Voxel GI data is an imported resource.")); + return; + } + } } + voxel_gi->bake(); } } diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp index 727b2c9b4d..88fb88de98 100644 --- a/editor/project_converter_3_to_4.cpp +++ b/editor/project_converter_3_to_4.cpp @@ -1286,6 +1286,7 @@ static const char *gdscript_signals_renames[][2] = { { "about_to_show", "about_to_popup" }, // Popup { "button_release", "button_released" }, // XRController3D { "cancelled", "canceled" }, // AcceptDialog + { "item_double_clicked", "item_icon_double_clicked" }, // Tree { "network_peer_connected", "peer_connected" }, // MultiplayerAPI { "network_peer_disconnected", "peer_disconnected" }, // MultiplayerAPI { "network_peer_packet", "peer_packet" }, // MultiplayerAPI diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 5d1a2a49c5..86e77fbbbe 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -2191,7 +2191,7 @@ void SceneTreeDock::_do_create(Node *p_parent) { ERR_FAIL_COND(!child); String new_name = p_parent->validate_child_name(child); - if (GLOBAL_GET("editor/node_naming/name_casing").operator int() != NAME_CASING_PASCAL_CASE) { + if (GLOBAL_GET("editor/naming/node_name_casing").operator int() != NAME_CASING_PASCAL_CASE) { new_name = adjust_name_casing(new_name); } child->set_name(new_name); @@ -3613,7 +3613,7 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec scene_tree->connect("script_dropped", callable_mp(this, &SceneTreeDock::_script_dropped)); scene_tree->connect("nodes_dragged", callable_mp(this, &SceneTreeDock::_nodes_drag_begin)); - scene_tree->get_scene_tree()->connect("item_double_clicked", callable_mp(this, &SceneTreeDock::_focus_node)); + scene_tree->get_scene_tree()->connect("item_icon_double_clicked", callable_mp(this, &SceneTreeDock::_focus_node)); editor_selection->connect("selection_changed", callable_mp(this, &SceneTreeDock::_selection_changed)); diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index e795cc919c..42801cdaf1 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -971,7 +971,7 @@ void SceneTreeEditor::_renamed() { String raw_new_name = which->get_text(0); if (raw_new_name.strip_edges().is_empty()) { // If name is empty, fallback to class name. - if (GLOBAL_GET("editor/node_naming/name_casing").operator int() != NAME_CASING_PASCAL_CASE) { + if (GLOBAL_GET("editor/naming/node_name_casing").operator int() != NAME_CASING_PASCAL_CASE) { raw_new_name = Node::adjust_name_casing(n->get_class()); } else { raw_new_name = n->get_class(); |