summaryrefslogtreecommitdiffstats
path: root/editor
diff options
context:
space:
mode:
Diffstat (limited to 'editor')
-rw-r--r--editor/code_editor.cpp18
-rw-r--r--editor/editor_asset_installer.cpp439
-rw-r--r--editor/editor_asset_installer.h38
-rw-r--r--editor/editor_node.cpp12
-rw-r--r--editor/editor_node.h1
-rw-r--r--editor/editor_properties.cpp2
-rw-r--r--editor/editor_themes.cpp4
-rw-r--r--editor/filesystem_dock.cpp8
-rw-r--r--editor/gui/editor_spin_slider.cpp27
-rw-r--r--editor/plugins/bone_map_editor_plugin.cpp185
-rw-r--r--editor/plugins/bone_map_editor_plugin.h1
-rw-r--r--editor/plugins/navigation_polygon_editor_plugin.cpp88
-rw-r--r--editor/plugins/navigation_polygon_editor_plugin.h26
-rw-r--r--editor/plugins/script_text_editor.cpp6
-rw-r--r--editor/plugins/script_text_editor.h1
-rw-r--r--editor/plugins/text_editor.cpp4
-rw-r--r--editor/plugins/text_editor.h1
-rw-r--r--editor/plugins/text_shader_editor.cpp4
-rw-r--r--editor/plugins/text_shader_editor.h1
-rw-r--r--editor/script_create_dialog.cpp9
-rw-r--r--editor/script_create_dialog.h1
21 files changed, 663 insertions, 213 deletions
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 876fef078b..56e405bfcf 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -805,6 +805,11 @@ void CodeTextEditor::input(const Ref<InputEvent> &event) {
accept_event();
return;
}
+ if (ED_IS_SHORTCUT("script_text_editor/duplicate_lines", key_event)) {
+ text_editor->duplicate_lines();
+ accept_event();
+ return;
+ }
}
void CodeTextEditor::_text_editor_gui_input(const Ref<InputEvent> &p_event) {
@@ -1487,14 +1492,22 @@ void CodeTextEditor::toggle_inline_comment(const String &delimiter) {
}
// Check first if there's any uncommented lines in selection.
bool is_commented = true;
+ bool is_all_empty = true;
for (int line = from; line <= to; line++) {
// `+ delimiter.length()` here because comment delimiter is not actually `in comment` so we check first character after it
int delimiter_idx = text_editor->is_in_comment(line, text_editor->get_first_non_whitespace_column(line) + delimiter.length());
- if (delimiter_idx == -1 || text_editor->get_delimiter_start_key(delimiter_idx) != delimiter) {
+ // Empty lines should not be counted.
+ bool is_empty = text_editor->get_line(line).strip_edges().is_empty();
+ is_all_empty = is_all_empty && is_empty;
+ if (!is_empty && (delimiter_idx == -1 || text_editor->get_delimiter_start_key(delimiter_idx) != delimiter)) {
is_commented = false;
break;
}
}
+
+ // Special case for commenting empty lines, treat it/them as uncommented lines.
+ is_commented = is_commented && !is_all_empty;
+
// Caret positions need to be saved since they could be moved at the eol.
Vector<int> caret_cols;
Vector<int> selection_to_cols;
@@ -1510,10 +1523,11 @@ void CodeTextEditor::toggle_inline_comment(const String &delimiter) {
// Comment/uncomment.
for (int line = from; line <= to; line++) {
String line_text = text_editor->get_line(line);
- if (line_text.strip_edges().is_empty()) {
+ if (is_all_empty) {
text_editor->set_line(line, delimiter);
continue;
}
+
if (is_commented) {
text_editor->set_line(line, line_text.replace_first(delimiter, ""));
} else {
diff --git a/editor/editor_asset_installer.cpp b/editor/editor_asset_installer.cpp
index b2d21c3447..b609de9f67 100644
--- a/editor/editor_asset_installer.cpp
+++ b/editor/editor_asset_installer.cpp
@@ -35,31 +35,48 @@
#include "core/io/zip_io.h"
#include "editor/editor_file_system.h"
#include "editor/editor_node.h"
+#include "editor/editor_scale.h"
#include "editor/editor_string_names.h"
+#include "editor/gui/editor_file_dialog.h"
#include "editor/gui/editor_toaster.h"
#include "editor/progress_dialog.h"
#include "scene/gui/check_box.h"
#include "scene/gui/label.h"
+#include "scene/gui/link_button.h"
+#include "scene/gui/separator.h"
+#include "scene/gui/split_container.h"
-void EditorAssetInstaller::_item_checked() {
- if (updating || !tree->get_edited()) {
+void EditorAssetInstaller::_item_checked_cbk() {
+ if (updating_source || !source_tree->get_edited()) {
return;
}
- updating = true;
- TreeItem *item = tree->get_edited();
+ updating_source = true;
+ TreeItem *item = source_tree->get_edited();
item->propagate_check(0);
- updating = false;
+ _update_confirm_button();
+ _rebuild_destination_tree();
+ updating_source = false;
}
-void EditorAssetInstaller::_check_propagated_to_item(Object *p_obj, int column) {
+void EditorAssetInstaller::_check_propagated_to_item(Object *p_obj, int p_column) {
TreeItem *affected_item = Object::cast_to<TreeItem>(p_obj);
- if (affected_item && affected_item->get_custom_color(0) != Color()) {
+ if (!affected_item) {
+ return;
+ }
+
+ Dictionary item_meta = affected_item->get_metadata(0);
+ bool is_conflict = item_meta.get("is_conflict", false);
+ if (is_conflict) {
affected_item->set_checked(0, false);
affected_item->propagate_check(0, false);
}
}
+bool EditorAssetInstaller::_is_item_checked(const String &p_source_path) const {
+ return file_item_map.has(p_source_path) && (file_item_map[p_source_path]->is_checked(0) || file_item_map[p_source_path]->is_indeterminate(0));
+}
+
void EditorAssetInstaller::open_asset(const String &p_path, bool p_autoskip_toplevel) {
package_path = p_path;
asset_files.clear();
@@ -104,27 +121,24 @@ void EditorAssetInstaller::open_asset(const String &p_path, bool p_autoskip_topl
unzClose(pkg);
+ asset_title_label->set_text(asset_name);
+
_check_has_toplevel();
// Default to false, unless forced.
skip_toplevel = p_autoskip_toplevel;
+ skip_toplevel_check->set_block_signals(true);
skip_toplevel_check->set_pressed(!skip_toplevel_check->is_disabled() && skip_toplevel);
+ skip_toplevel_check->set_block_signals(false);
- _rebuild_tree();
-}
-
-void EditorAssetInstaller::_rebuild_tree() {
- updating = true;
- tree->clear();
+ _update_file_mappings();
+ _rebuild_source_tree();
+ _rebuild_destination_tree();
- TreeItem *root = tree->create_item();
- root->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
- root->set_checked(0, true);
- root->set_icon(0, get_theme_icon(SNAME("folder"), SNAME("FileDialog")));
- root->set_text(0, "res://");
- root->set_editable(0, true);
+ popup_centered_clamped(Size2(620, 640) * EDSCALE);
+}
- HashMap<String, TreeItem *> directory_item_map;
- int num_file_conflicts = 0;
+void EditorAssetInstaller::_update_file_mappings() {
+ mapped_files.clear();
bool first = true;
for (const String &E : asset_files) {
@@ -141,9 +155,31 @@ void EditorAssetInstaller::_rebuild_tree() {
path = path.trim_prefix(toplevel_prefix);
}
+ mapped_files[E] = path;
+ }
+}
+
+void EditorAssetInstaller::_rebuild_source_tree() {
+ updating_source = true;
+ source_tree->clear();
+
+ TreeItem *root = source_tree->create_item();
+ root->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ root->set_checked(0, true);
+ root->set_icon(0, get_theme_icon(SNAME("folder"), SNAME("FileDialog")));
+ root->set_text(0, "/");
+ root->set_editable(0, true);
+
+ file_item_map.clear();
+ HashMap<String, TreeItem *> directory_item_map;
+ int num_file_conflicts = 0;
+ first_file_conflict = nullptr;
+
+ for (const String &E : asset_files) {
+ String path = E; // We're going to mutate it.
+
bool is_directory = false;
if (path.ends_with("/")) {
- // Directory.
path = path.trim_suffix("/");
is_directory = true;
}
@@ -162,45 +198,155 @@ void EditorAssetInstaller::_rebuild_tree() {
TreeItem *ti;
if (is_directory) {
- ti = _create_dir_item(parent_item, path, directory_item_map);
+ ti = _create_dir_item(source_tree, parent_item, path, directory_item_map);
} else {
- ti = _create_file_item(parent_item, path, &num_file_conflicts);
+ ti = _create_file_item(source_tree, parent_item, path, &num_file_conflicts);
}
file_item_map[E] = ti;
}
- asset_title_label->set_text(asset_name);
- if (num_file_conflicts >= 1) {
- asset_conflicts_label->set_text(vformat(TTRN("%d file conflicts with your project", "%d files conflict with your project", num_file_conflicts), num_file_conflicts));
- asset_conflicts_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
+ _update_conflict_status(num_file_conflicts);
+ _update_confirm_button();
+
+ updating_source = false;
+}
+
+void EditorAssetInstaller::_update_source_tree() {
+ int num_file_conflicts = 0;
+ first_file_conflict = nullptr;
+
+ for (const KeyValue<String, TreeItem *> &E : file_item_map) {
+ TreeItem *ti = E.value;
+ Dictionary item_meta = ti->get_metadata(0);
+ if ((bool)item_meta.get("is_dir", false)) {
+ continue;
+ }
+
+ String asset_path = item_meta.get("asset_path", "");
+ ERR_CONTINUE(asset_path.is_empty());
+
+ bool target_exists = _update_source_item_status(ti, asset_path);
+ if (target_exists) {
+ if (first_file_conflict == nullptr) {
+ first_file_conflict = ti;
+ }
+ num_file_conflicts += 1;
+ }
+
+ item_meta["is_conflict"] = target_exists;
+ ti->set_metadata(0, item_meta);
+ }
+
+ _update_conflict_status(num_file_conflicts);
+ _update_confirm_button();
+}
+
+bool EditorAssetInstaller::_update_source_item_status(TreeItem *p_item, const String &p_path) {
+ ERR_FAIL_COND_V(!mapped_files.has(p_path), false);
+ String target_path = target_dir_path.path_join(mapped_files[p_path]);
+
+ bool target_exists = FileAccess::exists(target_path);
+ if (target_exists) {
+ p_item->set_custom_color(0, get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
+ p_item->set_tooltip_text(0, vformat(TTR("%s (already exists)"), target_path));
+ p_item->set_checked(0, false);
} else {
- asset_conflicts_label->set_text(TTR("No files conflict with your project"));
- asset_conflicts_label->remove_theme_color_override("font_color");
+ p_item->clear_custom_color(0);
+ p_item->set_tooltip_text(0, target_path);
+ p_item->set_checked(0, true);
}
- popup_centered_ratio(0.5);
- updating = false;
+ p_item->propagate_check(0);
+ return target_exists;
}
-TreeItem *EditorAssetInstaller::_create_dir_item(TreeItem *p_parent, const String &p_path, HashMap<String, TreeItem *> &p_item_map) {
- TreeItem *ti = tree->create_item(p_parent);
- ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
- ti->set_checked(0, true);
- ti->set_editable(0, true);
+void EditorAssetInstaller::_rebuild_destination_tree() {
+ destination_tree->clear();
+
+ TreeItem *root = destination_tree->create_item();
+ root->set_icon(0, get_theme_icon(SNAME("folder"), SNAME("FileDialog")));
+ root->set_text(0, target_dir_path + (target_dir_path == "res://" ? "" : "/"));
+
+ HashMap<String, TreeItem *> directory_item_map;
+
+ for (const KeyValue<String, String> &E : mapped_files) {
+ if (!_is_item_checked(E.key)) {
+ continue;
+ }
+
+ String path = E.value; // We're going to mutate it.
+
+ bool is_directory = false;
+ if (path.ends_with("/")) {
+ path = path.trim_suffix("/");
+ is_directory = true;
+ }
+
+ TreeItem *parent_item;
+
+ int separator = path.rfind("/");
+ if (separator == -1) {
+ parent_item = root;
+ } else {
+ String parent_path = path.substr(0, separator);
+ HashMap<String, TreeItem *>::Iterator I = directory_item_map.find(parent_path);
+ ERR_CONTINUE(!I);
+ parent_item = I->value;
+ }
+
+ if (is_directory) {
+ _create_dir_item(destination_tree, parent_item, path, directory_item_map);
+ } else {
+ int num_file_conflicts = 0; // Don't need it, but need to pass something.
+ _create_file_item(destination_tree, parent_item, path, &num_file_conflicts);
+ }
+ }
+}
+
+TreeItem *EditorAssetInstaller::_create_dir_item(Tree *p_tree, TreeItem *p_parent, const String &p_path, HashMap<String, TreeItem *> &p_item_map) {
+ TreeItem *ti = p_tree->create_item(p_parent);
+
+ if (p_tree == source_tree) {
+ ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ ti->set_editable(0, true);
+ ti->set_checked(0, true);
+ ti->propagate_check(0);
+
+ Dictionary meta;
+ meta["asset_path"] = p_path + "/";
+ meta["is_dir"] = true;
+ meta["is_conflict"] = false;
+ ti->set_metadata(0, meta);
+ }
ti->set_text(0, p_path.get_file() + "/");
ti->set_icon(0, get_theme_icon(SNAME("folder"), SNAME("FileDialog")));
- ti->set_metadata(0, String());
p_item_map[p_path] = ti;
return ti;
}
-TreeItem *EditorAssetInstaller::_create_file_item(TreeItem *p_parent, const String &p_path, int *r_conflicts) {
- TreeItem *ti = tree->create_item(p_parent);
- ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
- ti->set_checked(0, true);
- ti->set_editable(0, true);
+TreeItem *EditorAssetInstaller::_create_file_item(Tree *p_tree, TreeItem *p_parent, const String &p_path, int *r_conflicts) {
+ TreeItem *ti = p_tree->create_item(p_parent);
+
+ if (p_tree == source_tree) {
+ ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ ti->set_editable(0, true);
+
+ bool target_exists = _update_source_item_status(ti, p_path);
+ if (target_exists) {
+ if (first_file_conflict == nullptr) {
+ first_file_conflict = ti;
+ }
+ *r_conflicts += 1;
+ }
+
+ Dictionary meta;
+ meta["asset_path"] = p_path;
+ meta["is_dir"] = false;
+ meta["is_conflict"] = target_exists;
+ ti->set_metadata(0, meta);
+ }
String file = p_path.get_file();
String extension = file.get_extension().to_lower();
@@ -211,20 +357,38 @@ TreeItem *EditorAssetInstaller::_create_file_item(TreeItem *p_parent, const Stri
}
ti->set_text(0, file);
- String res_path = "res://" + p_path;
- if (FileAccess::exists(res_path)) {
- *r_conflicts += 1;
- ti->set_custom_color(0, get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
- ti->set_tooltip_text(0, vformat(TTR("%s (already exists)"), res_path));
- ti->set_checked(0, false);
- ti->propagate_check(0);
+ return ti;
+}
+
+void EditorAssetInstaller::_update_conflict_status(int p_conflicts) {
+ if (p_conflicts >= 1) {
+ asset_conflicts_link->set_text(vformat(TTRN("%d file conflicts with your project and won't be installed", "%d files conflict with your project and won't be installed", p_conflicts), p_conflicts));
+ asset_conflicts_link->show();
+ asset_conflicts_label->hide();
} else {
- ti->set_tooltip_text(0, res_path);
+ asset_conflicts_link->hide();
+ asset_conflicts_label->show();
}
+}
- ti->set_metadata(0, res_path);
+void EditorAssetInstaller::_update_confirm_button() {
+ TreeItem *root = source_tree->get_root();
+ get_ok_button()->set_disabled(!root || (!root->is_checked(0) && !root->is_indeterminate(0)));
+}
- return ti;
+void EditorAssetInstaller::_toggle_source_tree(bool p_visible, bool p_scroll_to_error) {
+ source_tree_vb->set_visible(p_visible);
+ show_source_files_button->set_pressed_no_signal(p_visible); // To keep in sync if triggered by something else.
+
+ if (p_visible) {
+ show_source_files_button->set_icon(get_editor_theme_icon(SNAME("Back")));
+ } else {
+ show_source_files_button->set_icon(get_editor_theme_icon(SNAME("Forward")));
+ }
+
+ if (p_visible && p_scroll_to_error && first_file_conflict) {
+ source_tree->scroll_to_item(first_file_conflict, true);
+ }
}
void EditorAssetInstaller::_check_has_toplevel() {
@@ -259,12 +423,42 @@ void EditorAssetInstaller::_check_has_toplevel() {
toplevel_prefix = first_asset;
skip_toplevel_check->set_disabled(false);
- skip_toplevel_check->set_tooltip_text("");
+ skip_toplevel_check->set_tooltip_text(TTR("Ignore the root directory when extracting files."));
}
void EditorAssetInstaller::_set_skip_toplevel(bool p_checked) {
+ if (skip_toplevel == p_checked) {
+ return;
+ }
+
skip_toplevel = p_checked;
- _rebuild_tree();
+ _update_file_mappings();
+ _update_source_tree();
+ _rebuild_destination_tree();
+}
+
+void EditorAssetInstaller::_open_target_dir_dialog() {
+ if (!target_dir_dialog) {
+ target_dir_dialog = memnew(EditorFileDialog);
+ target_dir_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR);
+ target_dir_dialog->set_title(TTR("Select Install Folder"));
+ target_dir_dialog->set_current_dir(target_dir_path);
+ target_dir_dialog->connect("dir_selected", callable_mp(this, &EditorAssetInstaller::_target_dir_selected));
+ add_child(target_dir_dialog);
+ }
+
+ target_dir_dialog->popup_file_dialog();
+}
+
+void EditorAssetInstaller::_target_dir_selected(const String &p_target_path) {
+ if (target_dir_path == p_target_path) {
+ return;
+ }
+
+ target_dir_path = p_target_path;
+ _update_file_mappings();
+ _update_source_tree();
+ _rebuild_destination_tree();
}
void EditorAssetInstaller::ok_pressed() {
@@ -296,26 +490,25 @@ void EditorAssetInstaller::_install_asset() {
}
String source_name = String::utf8(fname);
- if (!file_item_map.has(source_name) || (!file_item_map[source_name]->is_checked(0) && !file_item_map[source_name]->is_indeterminate(0))) {
+ if (!_is_item_checked(source_name)) {
continue;
}
- String path = file_item_map[source_name]->get_metadata(0);
- if (path.is_empty()) { // Directory.
- // TODO: Metadata can be used to store the entire path of directories too,
- // so this tree iteration can be avoided.
- String dir_path;
- TreeItem *t = file_item_map[source_name];
- while (t) {
- dir_path = t->get_text(0) + dir_path;
- t = t->get_parent();
- }
+ HashMap<String, String>::Iterator E = mapped_files.find(source_name);
+ if (!E) {
+ continue; // No remapped path means we don't want it; most likely the root.
+ }
+
+ String target_path = target_dir_path.path_join(E->value);
- if (dir_path.ends_with("/")) {
- dir_path = dir_path.substr(0, dir_path.length() - 1);
+ Dictionary asset_meta = file_item_map[source_name]->get_metadata(0);
+ bool is_dir = asset_meta.get("is_dir", false);
+ if (is_dir) {
+ if (target_path.ends_with("/")) {
+ target_path = target_path.substr(0, target_path.length() - 1);
}
- da->make_dir_recursive(dir_path);
+ da->make_dir_recursive(target_path);
} else {
Vector<uint8_t> uncomp_data;
uncomp_data.resize(info.uncompressed_size);
@@ -325,16 +518,16 @@ void EditorAssetInstaller::_install_asset() {
unzCloseCurrentFile(pkg);
// Ensure that the target folder exists.
- da->make_dir_recursive(path.get_base_dir());
+ da->make_dir_recursive(target_path.get_base_dir());
- Ref<FileAccess> f = FileAccess::open(path, FileAccess::WRITE);
+ Ref<FileAccess> f = FileAccess::open(target_path, FileAccess::WRITE);
if (f.is_valid()) {
f->store_buffer(uncomp_data.ptr(), uncomp_data.size());
} else {
- failed_files.push_back(path);
+ failed_files.push_back(target_path);
}
- ProgressDialog::get_singleton()->task_step("uncompress", path, idx);
+ ProgressDialog::get_singleton()->task_step("uncompress", target_path, idx);
}
}
@@ -358,6 +551,7 @@ void EditorAssetInstaller::_install_asset() {
EditorNode::get_singleton()->show_warning(vformat(TTR("Asset \"%s\" installed successfully!"), asset_name), TTR("Success!"));
}
}
+
EditorFileSystem::get_singleton()->scan_changes();
}
@@ -372,6 +566,13 @@ String EditorAssetInstaller::get_asset_name() const {
void EditorAssetInstaller::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
+ if (show_source_files_button->is_pressed()) {
+ show_source_files_button->set_icon(get_editor_theme_icon(SNAME("Back")));
+ } else {
+ show_source_files_button->set_icon(get_editor_theme_icon(SNAME("Forward")));
+ }
+ asset_conflicts_link->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
+
generic_extension_icon = get_editor_theme_icon(SNAME("Object"));
extension_icon_map.clear();
@@ -435,36 +636,98 @@ EditorAssetInstaller::EditorAssetInstaller() {
VBoxContainer *vb = memnew(VBoxContainer);
add_child(vb);
+ // Status bar.
+
HBoxContainer *asset_status = memnew(HBoxContainer);
vb->add_child(asset_status);
Label *asset_label = memnew(Label);
- asset_label->set_text(TTR("Contents of asset:"));
+ asset_label->set_text(TTR("Asset:"));
+ asset_label->set_theme_type_variation("HeaderSmall");
asset_status->add_child(asset_label);
asset_title_label = memnew(Label);
- asset_title_label->set_theme_type_variation("HeaderSmall");
asset_status->add_child(asset_title_label);
- asset_status->add_spacer();
+ // File remapping controls.
- asset_conflicts_label = memnew(Label);
- asset_conflicts_label->set_theme_type_variation("HeaderSmall");
- asset_status->add_child(asset_conflicts_label);
+ HBoxContainer *remapping_tools = memnew(HBoxContainer);
+ vb->add_child(remapping_tools);
+
+ show_source_files_button = memnew(Button);
+ show_source_files_button->set_toggle_mode(true);
+ show_source_files_button->set_tooltip_text(TTR("Open the list of the asset contents and select which files to install."));
+ remapping_tools->add_child(show_source_files_button);
+ show_source_files_button->connect("toggled", callable_mp(this, &EditorAssetInstaller::_toggle_source_tree).bind(false));
+
+ Button *target_dir_button = memnew(Button);
+ target_dir_button->set_text(TTR("Change Install Folder"));
+ target_dir_button->set_tooltip_text(TTR("Change the folder where the contents of the asset are going to be installed."));
+ remapping_tools->add_child(target_dir_button);
+ target_dir_button->connect("pressed", callable_mp(this, &EditorAssetInstaller::_open_target_dir_dialog));
+
+ remapping_tools->add_child(memnew(VSeparator));
skip_toplevel_check = memnew(CheckBox);
- skip_toplevel_check->set_text(TTR("Ignore the root directory when extracting files"));
- skip_toplevel_check->set_h_size_flags(Control::SIZE_SHRINK_BEGIN);
+ skip_toplevel_check->set_text(TTR("Ignore asset root"));
+ skip_toplevel_check->set_tooltip_text(TTR("Ignore the root directory when extracting files."));
skip_toplevel_check->connect("toggled", callable_mp(this, &EditorAssetInstaller::_set_skip_toplevel));
- vb->add_child(skip_toplevel_check);
+ remapping_tools->add_child(skip_toplevel_check);
- tree = memnew(Tree);
- tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- tree->connect("item_edited", callable_mp(this, &EditorAssetInstaller::_item_checked));
- tree->connect("check_propagated_to_item", callable_mp(this, &EditorAssetInstaller::_check_propagated_to_item));
- vb->add_child(tree);
+ remapping_tools->add_spacer();
- set_title(TTR("Select Asset Files to Install"));
+ asset_conflicts_label = memnew(Label);
+ asset_conflicts_label->set_theme_type_variation("HeaderSmall");
+ asset_conflicts_label->set_text(TTR("No files conflict with your project"));
+ remapping_tools->add_child(asset_conflicts_label);
+ asset_conflicts_link = memnew(LinkButton);
+ asset_conflicts_link->set_theme_type_variation("HeaderSmallLink");
+ asset_conflicts_link->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
+ asset_conflicts_link->set_tooltip_text(TTR("Show contents of the asset and conflicting files."));
+ asset_conflicts_link->set_visible(false);
+ remapping_tools->add_child(asset_conflicts_link);
+ asset_conflicts_link->connect("pressed", callable_mp(this, &EditorAssetInstaller::_toggle_source_tree).bind(true, true));
+
+ // File hierarchy trees.
+
+ HSplitContainer *tree_split = memnew(HSplitContainer);
+ tree_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ vb->add_child(tree_split);
+
+ source_tree_vb = memnew(VBoxContainer);
+ source_tree_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ source_tree_vb->set_visible(show_source_files_button->is_pressed());
+ tree_split->add_child(source_tree_vb);
+
+ Label *source_tree_label = memnew(Label);
+ source_tree_label->set_text(TTR("Contents of the asset:"));
+ source_tree_label->set_theme_type_variation("HeaderSmall");
+ source_tree_vb->add_child(source_tree_label);
+
+ source_tree = memnew(Tree);
+ source_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ source_tree->connect("item_edited", callable_mp(this, &EditorAssetInstaller::_item_checked_cbk));
+ source_tree->connect("check_propagated_to_item", callable_mp(this, &EditorAssetInstaller::_check_propagated_to_item));
+ source_tree_vb->add_child(source_tree);
+
+ VBoxContainer *destination_tree_vb = memnew(VBoxContainer);
+ destination_tree_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ tree_split->add_child(destination_tree_vb);
+
+ Label *destination_tree_label = memnew(Label);
+ destination_tree_label->set_text(TTR("Installation preview:"));
+ destination_tree_label->set_theme_type_variation("HeaderSmall");
+ destination_tree_vb->add_child(destination_tree_label);
+
+ destination_tree = memnew(Tree);
+ destination_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ destination_tree->connect("item_edited", callable_mp(this, &EditorAssetInstaller::_item_checked_cbk));
+ destination_tree->connect("check_propagated_to_item", callable_mp(this, &EditorAssetInstaller::_check_propagated_to_item));
+ destination_tree_vb->add_child(destination_tree);
+
+ // Dialog configuration.
+
+ set_title(TTR("Configure Asset Before Installing"));
set_ok_button_text(TTR("Install"));
set_hide_on_ok(true);
}
diff --git a/editor/editor_asset_installer.h b/editor/editor_asset_installer.h
index f5e094f8d5..2030715b51 100644
--- a/editor/editor_asset_installer.h
+++ b/editor/editor_asset_installer.h
@@ -35,37 +35,61 @@
#include "scene/gui/tree.h"
class CheckBox;
+class EditorFileDialog;
class Label;
+class LinkButton;
class EditorAssetInstaller : public ConfirmationDialog {
GDCLASS(EditorAssetInstaller, ConfirmationDialog);
- Tree *tree = nullptr;
+ VBoxContainer *source_tree_vb = nullptr;
+ Tree *source_tree = nullptr;
+ Tree *destination_tree = nullptr;
Label *asset_title_label = nullptr;
Label *asset_conflicts_label = nullptr;
+ LinkButton *asset_conflicts_link = nullptr;
+
+ Button *show_source_files_button = nullptr;
CheckBox *skip_toplevel_check = nullptr;
+ EditorFileDialog *target_dir_dialog = nullptr;
String package_path;
String asset_name;
HashSet<String> asset_files;
+ HashMap<String, String> mapped_files;
HashMap<String, TreeItem *> file_item_map;
+ TreeItem *first_file_conflict = nullptr;
+
Ref<Texture2D> generic_extension_icon;
HashMap<String, Ref<Texture2D>> extension_icon_map;
- bool updating = false;
+ bool updating_source = false;
String toplevel_prefix;
bool skip_toplevel = false;
+ String target_dir_path = "res://";
void _check_has_toplevel();
void _set_skip_toplevel(bool p_checked);
- void _rebuild_tree();
- TreeItem *_create_dir_item(TreeItem *p_parent, const String &p_path, HashMap<String, TreeItem *> &p_item_map);
- TreeItem *_create_file_item(TreeItem *p_parent, const String &p_path, int *r_conflicts);
+ void _open_target_dir_dialog();
+ void _target_dir_selected(const String &p_target_path);
+
+ void _update_file_mappings();
+ void _rebuild_source_tree();
+ void _update_source_tree();
+ bool _update_source_item_status(TreeItem *p_item, const String &p_path);
+ void _rebuild_destination_tree();
+ TreeItem *_create_dir_item(Tree *p_tree, TreeItem *p_parent, const String &p_path, HashMap<String, TreeItem *> &p_item_map);
+ TreeItem *_create_file_item(Tree *p_tree, TreeItem *p_parent, const String &p_path, int *r_conflicts);
+
+ void _update_conflict_status(int p_conflicts);
+ void _update_confirm_button();
+ void _toggle_source_tree(bool p_visible, bool p_scroll_to_error = false);
- void _item_checked();
- void _check_propagated_to_item(Object *p_obj, int column);
+ void _item_checked_cbk();
+ void _check_propagated_to_item(Object *p_obj, int p_column);
+ bool _is_item_checked(const String &p_source_path) const;
void _install_asset();
virtual void ok_pressed() override;
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 223e50f557..dab76e9460 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -31,6 +31,7 @@
#include "editor_node.h"
#include "core/config/project_settings.h"
+#include "core/extension/gdextension_manager.h"
#include "core/input/input.h"
#include "core/io/config_file.h"
#include "core/io/file_access.h"
@@ -435,6 +436,11 @@ void EditorNode::_update_from_settings() {
#endif // DEBUG_ENABLED
}
+void EditorNode::_gdextensions_reloaded() {
+ // In case the developer is inspecting an object that will be changed by the reload.
+ InspectorDock::get_inspector_singleton()->update_tree();
+}
+
void EditorNode::_select_default_main_screen_plugin() {
if (EDITOR_3D < main_editor_buttons.size() && main_editor_buttons[EDITOR_3D]->is_visible()) {
// If the 3D editor is enabled, use this as the default.
@@ -714,6 +720,9 @@ void EditorNode::_notification(int p_what) {
EditorFileSystem::get_singleton()->scan_changes();
_scan_external_changes();
+
+ GDExtensionManager *gdextension_manager = GDExtensionManager::get_singleton();
+ callable_mp(gdextension_manager, &GDExtensionManager::reload_extensions).call_deferred();
} break;
case NOTIFICATION_APPLICATION_FOCUS_OUT: {
@@ -3269,7 +3278,7 @@ void EditorNode::remove_extension_editor_plugin(const StringName &p_class_name)
EditorPlugin *plugin = singleton->editor_data.get_extension_editor_plugin(p_class_name);
remove_editor_plugin(plugin);
- memfree(plugin);
+ memdelete(plugin);
singleton->editor_data.remove_extension_editor_plugin(p_class_name);
}
@@ -6710,6 +6719,7 @@ EditorNode::EditorNode() {
EditorUndoRedoManager::get_singleton()->connect("version_changed", callable_mp(this, &EditorNode::_update_undo_redo_allowed));
EditorUndoRedoManager::get_singleton()->connect("history_changed", callable_mp(this, &EditorNode::_update_undo_redo_allowed));
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &EditorNode::_update_from_settings));
+ GDExtensionManager::get_singleton()->connect("extensions_reloaded", callable_mp(this, &EditorNode::_gdextensions_reloaded));
TranslationServer::get_singleton()->set_enabled(false);
// Load settings.
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 5ecb3186e1..72134e283b 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -597,6 +597,7 @@ private:
void _add_dropped_files_recursive(const Vector<String> &p_files, String to_path);
void _update_from_settings();
+ void _gdextensions_reloaded();
void _renderer_selected(int);
void _update_renderer_color();
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index 2fa0641132..a197eb9dce 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -2818,7 +2818,7 @@ void EditorPropertyNodePath::_node_assign() {
}
void EditorPropertyNodePath::_node_clear() {
- emit_changed(get_edited_property(), NodePath());
+ emit_changed(get_edited_property(), Variant());
update_property();
}
diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp
index ca2a2ff1d6..6897052ce3 100644
--- a/editor/editor_themes.cpp
+++ b/editor/editor_themes.cpp
@@ -1917,6 +1917,10 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_constant("outline_size", "LinkButton", 0);
+ theme->set_type_variation("HeaderSmallLink", "LinkButton");
+ theme->set_font("font", "HeaderSmallLink", theme->get_font(SNAME("font"), SNAME("HeaderSmall")));
+ theme->set_font_size("font_size", "HeaderSmallLink", theme->get_font_size(SNAME("font_size"), SNAME("HeaderSmall")));
+
// TooltipPanel + TooltipLabel
// TooltipPanel is also used for custom tooltips, while TooltipLabel
// is only relevant for default tooltips.
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index 338376c724..a7a31bb77c 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -1970,15 +1970,15 @@ Vector<String> FileSystemDock::_tree_get_selected(bool remove_self_inclusion) co
Vector<String> selected_strings;
TreeItem *favorites_item = tree->get_root()->get_first_child();
- TreeItem *active_selected = tree->get_selected();
- if (active_selected && active_selected != favorites_item) {
- selected_strings.push_back(active_selected->get_metadata(0));
+ TreeItem *cursor_item = tree->get_selected();
+ if (cursor_item && cursor_item->is_selected(0) && cursor_item != favorites_item) {
+ selected_strings.push_back(cursor_item->get_metadata(0));
}
TreeItem *selected = tree->get_root();
selected = tree->get_next_selected(selected);
while (selected) {
- if (selected != active_selected && selected != favorites_item) {
+ if (selected != cursor_item && selected != favorites_item) {
selected_strings.push_back(selected->get_metadata(0));
}
selected = tree->get_next_selected(selected);
diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp
index c59822ba55..a1b0ecad6b 100644
--- a/editor/gui/editor_spin_slider.cpp
+++ b/editor/gui/editor_spin_slider.cpp
@@ -203,7 +203,6 @@ void EditorSpinSlider::_value_input_gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventKey> k = p_event;
if (k.is_valid() && k->is_pressed() && !is_read_only()) {
double step = get_step();
- double real_step = step;
if (step < 1) {
double divisor = 1.0 / get_step();
@@ -221,30 +220,22 @@ void EditorSpinSlider::_value_input_gui_input(const Ref<InputEvent> &p_event) {
}
Key code = k->get_keycode();
- switch (code) {
- case Key::UP: {
- _evaluate_input_text();
-
- double last_value = get_value();
- set_value(last_value + step);
- double new_value = get_value();
-
- if (new_value < CLAMP(last_value + step, get_min(), get_max())) {
- set_value(last_value + real_step);
- }
- value_input_dirty = true;
- set_process_internal(true);
- } break;
+ switch (code) {
+ case Key::UP:
case Key::DOWN: {
_evaluate_input_text();
double last_value = get_value();
- set_value(last_value - step);
+ if (code == Key::DOWN) {
+ step *= -1;
+ }
+ set_value(last_value + step);
double new_value = get_value();
- if (new_value > CLAMP(last_value - step, get_min(), get_max())) {
- set_value(last_value - real_step);
+ double clamp_value = CLAMP(new_value, get_min(), get_max());
+ if (new_value != clamp_value) {
+ set_value(clamp_value);
}
value_input_dirty = true;
diff --git a/editor/plugins/bone_map_editor_plugin.cpp b/editor/plugins/bone_map_editor_plugin.cpp
index 2c9cff3eff..7d15edef1e 100644
--- a/editor/plugins/bone_map_editor_plugin.cpp
+++ b/editor/plugins/bone_map_editor_plugin.cpp
@@ -533,6 +533,11 @@ void BoneMapper::_clear_mapping_current_group() {
}
#ifdef MODULE_REGEX_ENABLED
+bool BoneMapper::is_match_with_bone_name(String p_bone_name, String p_word) {
+ RegEx re = RegEx(p_word);
+ return !re.search(p_bone_name.to_lower()).is_null();
+}
+
int BoneMapper::search_bone_by_name(Skeleton3D *p_skeleton, Vector<String> p_picklist, BoneSegregation p_segregation, int p_parent, int p_child, int p_children_count) {
// There may be multiple candidates hit by existing the subsidiary bone.
// The one with the shortest name is probably the original.
@@ -540,7 +545,6 @@ int BoneMapper::search_bone_by_name(Skeleton3D *p_skeleton, Vector<String> p_pic
String shortest = "";
for (int word_idx = 0; word_idx < p_picklist.size(); word_idx++) {
- RegEx re = RegEx(p_picklist[word_idx]);
if (p_child == -1) {
Vector<int> bones_to_process = p_parent == -1 ? p_skeleton->get_parentless_bones() : p_skeleton->get_bone_children(p_parent);
while (bones_to_process.size() > 0) {
@@ -559,7 +563,7 @@ int BoneMapper::search_bone_by_name(Skeleton3D *p_skeleton, Vector<String> p_pic
}
String bn = skeleton->get_bone_name(idx);
- if (!re.search(bn.to_lower()).is_null() && guess_bone_segregation(bn) == p_segregation) {
+ if (is_match_with_bone_name(bn, p_picklist[word_idx]) && guess_bone_segregation(bn) == p_segregation) {
hit_list.push_back(bn);
}
}
@@ -584,7 +588,7 @@ int BoneMapper::search_bone_by_name(Skeleton3D *p_skeleton, Vector<String> p_pic
}
String bn = skeleton->get_bone_name(idx);
- if (!re.search(bn.to_lower()).is_null() && guess_bone_segregation(bn) == p_segregation) {
+ if (is_match_with_bone_name(bn, p_picklist[word_idx]) && guess_bone_segregation(bn) == p_segregation) {
hit_list.push_back(bn);
}
idx = skeleton->get_bone_parent(idx);
@@ -654,6 +658,7 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
picklist.push_back("pelvis");
picklist.push_back("waist");
picklist.push_back("torso");
+ picklist.push_back("spine");
int hips = search_bone_by_name(skeleton, picklist);
if (hips == -1) {
WARN_PRINT("Auto Mapping couldn't guess Hips. Abort auto mapping.");
@@ -704,70 +709,7 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
bone_idx = -1;
search_path.clear();
- // 3. Guess Neck
- picklist.push_back("neck");
- picklist.push_back("head"); // For no neck model.
- picklist.push_back("face"); // Same above.
- int neck = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, hips);
- picklist.clear();
-
- // 4. Guess Head
- picklist.push_back("head");
- picklist.push_back("face");
- int head = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, neck);
- if (head == -1) {
- search_path = skeleton->get_bone_children(neck);
- if (search_path.size() == 1) {
- head = search_path[0]; // Maybe only one child of the Neck is Head.
- }
- }
- if (head == -1) {
- if (neck != -1) {
- head = neck; // The head animation should have more movement.
- neck = -1;
- p_bone_map->_set_skeleton_bone_name("Head", skeleton->get_bone_name(head));
- } else {
- WARN_PRINT("Auto Mapping couldn't guess Neck or Head."); // Continued for guessing on the other bones. But abort when guessing spines step.
- }
- } else {
- p_bone_map->_set_skeleton_bone_name("Neck", skeleton->get_bone_name(neck));
- p_bone_map->_set_skeleton_bone_name("Head", skeleton->get_bone_name(head));
- }
- picklist.clear();
- search_path.clear();
-
- int neck_or_head = neck != -1 ? neck : (head != -1 ? head : -1);
- if (neck_or_head != -1) {
- // 4-1. Guess Eyes
- picklist.push_back("eye(?!.*(brow|lash|lid))");
- bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, neck_or_head);
- if (bone_idx == -1) {
- WARN_PRINT("Auto Mapping couldn't guess LeftEye.");
- } else {
- p_bone_map->_set_skeleton_bone_name("LeftEye", skeleton->get_bone_name(bone_idx));
- }
-
- bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, neck_or_head);
- if (bone_idx == -1) {
- WARN_PRINT("Auto Mapping couldn't guess RightEye.");
- } else {
- p_bone_map->_set_skeleton_bone_name("RightEye", skeleton->get_bone_name(bone_idx));
- }
- picklist.clear();
-
- // 4-2. Guess Jaw
- picklist.push_back("jaw");
- bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, neck_or_head);
- if (bone_idx == -1) {
- WARN_PRINT("Auto Mapping couldn't guess Jaw.");
- } else {
- p_bone_map->_set_skeleton_bone_name("Jaw", skeleton->get_bone_name(bone_idx));
- }
- bone_idx = -1;
- picklist.clear();
- }
-
- // 5. Guess Foots
+ // 3. Guess Foots
picklist.push_back("foot");
picklist.push_back("ankle");
int left_foot = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips);
@@ -784,7 +726,7 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
}
picklist.clear();
- // 5-1. Guess LowerLegs
+ // 3-1. Guess LowerLegs
picklist.push_back("(low|under).*leg");
picklist.push_back("knee");
picklist.push_back("shin");
@@ -810,7 +752,7 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
}
picklist.clear();
- // 5-2. Guess UpperLegs
+ // 3-2. Guess UpperLegs
picklist.push_back("up.*leg");
picklist.push_back("thigh");
picklist.push_back("leg");
@@ -834,7 +776,7 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
bone_idx = -1;
picklist.clear();
- // 5-3. Guess Toes
+ // 3-3. Guess Toes
picklist.push_back("toe");
picklist.push_back("ball");
if (left_foot != -1) {
@@ -871,7 +813,7 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
bone_idx = -1;
picklist.clear();
- // 6. Guess Hands
+ // 4. Guess Hands
picklist.push_back("hand");
picklist.push_back("wrist");
picklist.push_back("palm");
@@ -916,7 +858,7 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
bone_idx = -1;
picklist.clear();
- // 6-1. Guess Finger
+ // 4-1. Guess Finger
bool named_finger_is_found = false;
LocalVector<String> fingers;
fingers.push_back("thumb|pollex");
@@ -1106,7 +1048,7 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
}
}
- // 7. Guess Arms
+ // 5. Guess Arms
picklist.push_back("shoulder");
picklist.push_back("clavicle");
picklist.push_back("collar");
@@ -1124,7 +1066,7 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
}
picklist.clear();
- // 7-1. Guess LowerArms
+ // 5-1. Guess LowerArms
picklist.push_back("(low|fore).*arm");
picklist.push_back("elbow");
picklist.push_back("arm");
@@ -1148,7 +1090,7 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
}
picklist.clear();
- // 7-2. Guess UpperArms
+ // 5-2. Guess UpperArms
picklist.push_back("up.*arm");
picklist.push_back("arm");
if (left_shoulder != -1 && left_lower_arm != -1) {
@@ -1171,6 +1113,99 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
bone_idx = -1;
picklist.clear();
+ // 6. Guess Neck
+ picklist.push_back("neck");
+ picklist.push_back("head"); // For no neck model.
+ picklist.push_back("face"); // Same above.
+ int neck = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, hips);
+ picklist.clear();
+ if (neck == -1) {
+ // If it can't expect by name, search child spine of where the right and left shoulders (or hands) cross.
+ int ls_idx = left_shoulder != -1 ? left_shoulder : (left_hand_or_palm != -1 ? left_hand_or_palm : -1);
+ int rs_idx = right_shoulder != -1 ? right_shoulder : (right_hand_or_palm != -1 ? right_hand_or_palm : -1);
+ if (ls_idx != -1 && rs_idx != -1) {
+ bool detect = false;
+ while (ls_idx != hips && ls_idx >= 0 && rs_idx != hips && rs_idx >= 0) {
+ ls_idx = skeleton->get_bone_parent(ls_idx);
+ rs_idx = skeleton->get_bone_parent(rs_idx);
+ if (ls_idx == rs_idx) {
+ detect = true;
+ break;
+ }
+ }
+ if (detect) {
+ Vector<int> children = skeleton->get_bone_children(ls_idx);
+ children.erase(ls_idx);
+ children.erase(rs_idx);
+ String word = "spine"; // It would be better to limit the search with "spine" because it could be mistaken with breast, wing and etc...
+ for (int i = 0; children.size(); i++) {
+ bone_idx = children[i];
+ if (is_match_with_bone_name(skeleton->get_bone_name(bone_idx), word)) {
+ neck = bone_idx;
+ break;
+ };
+ }
+ bone_idx = -1;
+ }
+ }
+ }
+
+ // 7. Guess Head
+ picklist.push_back("head");
+ picklist.push_back("face");
+ int head = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, neck);
+ if (head == -1) {
+ search_path = skeleton->get_bone_children(neck);
+ if (search_path.size() == 1) {
+ head = search_path[0]; // Maybe only one child of the Neck is Head.
+ }
+ }
+ if (head == -1) {
+ if (neck != -1) {
+ head = neck; // The head animation should have more movement.
+ neck = -1;
+ p_bone_map->_set_skeleton_bone_name("Head", skeleton->get_bone_name(head));
+ } else {
+ WARN_PRINT("Auto Mapping couldn't guess Neck or Head."); // Continued for guessing on the other bones. But abort when guessing spines step.
+ }
+ } else {
+ p_bone_map->_set_skeleton_bone_name("Neck", skeleton->get_bone_name(neck));
+ p_bone_map->_set_skeleton_bone_name("Head", skeleton->get_bone_name(head));
+ }
+ picklist.clear();
+ search_path.clear();
+
+ int neck_or_head = neck != -1 ? neck : (head != -1 ? head : -1);
+ if (neck_or_head != -1) {
+ // 7-1. Guess Eyes
+ picklist.push_back("eye(?!.*(brow|lash|lid))");
+ bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, neck_or_head);
+ if (bone_idx == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess LeftEye.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("LeftEye", skeleton->get_bone_name(bone_idx));
+ }
+
+ bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, neck_or_head);
+ if (bone_idx == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess RightEye.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("RightEye", skeleton->get_bone_name(bone_idx));
+ }
+ picklist.clear();
+
+ // 7-2. Guess Jaw
+ picklist.push_back("jaw");
+ bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, neck_or_head);
+ if (bone_idx == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess Jaw.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("Jaw", skeleton->get_bone_name(bone_idx));
+ }
+ bone_idx = -1;
+ picklist.clear();
+ }
+
// 8. Guess UpperChest or Chest
if (neck_or_head == -1) {
return; // Abort.
diff --git a/editor/plugins/bone_map_editor_plugin.h b/editor/plugins/bone_map_editor_plugin.h
index 7974d241a2..9ff32707c7 100644
--- a/editor/plugins/bone_map_editor_plugin.h
+++ b/editor/plugins/bone_map_editor_plugin.h
@@ -179,6 +179,7 @@ class BoneMapper : public VBoxContainer {
BONE_SEGREGATION_LEFT,
BONE_SEGREGATION_RIGHT
};
+ bool is_match_with_bone_name(String p_bone_name, String p_word);
int search_bone_by_name(Skeleton3D *p_skeleton, Vector<String> p_picklist, BoneSegregation p_segregation = BONE_SEGREGATION_NONE, int p_parent = -1, int p_child = -1, int p_children_count = -1);
BoneSegregation guess_bone_segregation(String p_bone_name);
void auto_mapping_process(Ref<BoneMap> &p_bone_map);
diff --git a/editor/plugins/navigation_polygon_editor_plugin.cpp b/editor/plugins/navigation_polygon_editor_plugin.cpp
index 957a520d8a..48335f3b94 100644
--- a/editor/plugins/navigation_polygon_editor_plugin.cpp
+++ b/editor/plugins/navigation_polygon_editor_plugin.cpp
@@ -32,6 +32,8 @@
#include "editor/editor_node.h"
#include "editor/editor_undo_redo_manager.h"
+#include "scene/2d/navigation_region_2d.h"
+#include "scene/gui/dialogs.h"
Ref<NavigationPolygon> NavigationPolygonEditor::_ensure_navpoly() const {
Ref<NavigationPolygon> navpoly = node->get_navigation_polygon();
@@ -71,7 +73,6 @@ Variant NavigationPolygonEditor::_get_polygon(int p_idx) const {
void NavigationPolygonEditor::_set_polygon(int p_idx, const Variant &p_polygon) const {
Ref<NavigationPolygon> navpoly = _ensure_navpoly();
navpoly->set_outline(p_idx, p_polygon);
- navpoly->make_polygons_from_outlines();
}
void NavigationPolygonEditor::_action_add_polygon(const Variant &p_polygon) {
@@ -79,8 +80,6 @@ void NavigationPolygonEditor::_action_add_polygon(const Variant &p_polygon) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->add_do_method(navpoly.ptr(), "add_outline", p_polygon);
undo_redo->add_undo_method(navpoly.ptr(), "remove_outline", navpoly->get_outline_count());
- undo_redo->add_do_method(navpoly.ptr(), "make_polygons_from_outlines");
- undo_redo->add_undo_method(navpoly.ptr(), "make_polygons_from_outlines");
}
void NavigationPolygonEditor::_action_remove_polygon(int p_idx) {
@@ -88,8 +87,6 @@ void NavigationPolygonEditor::_action_remove_polygon(int p_idx) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->add_do_method(navpoly.ptr(), "remove_outline", p_idx);
undo_redo->add_undo_method(navpoly.ptr(), "add_outline_at_index", navpoly->get_outline(p_idx), p_idx);
- undo_redo->add_do_method(navpoly.ptr(), "make_polygons_from_outlines");
- undo_redo->add_undo_method(navpoly.ptr(), "make_polygons_from_outlines");
}
void NavigationPolygonEditor::_action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon) {
@@ -97,8 +94,6 @@ void NavigationPolygonEditor::_action_set_polygon(int p_idx, const Variant &p_pr
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->add_do_method(navpoly.ptr(), "set_outline", p_idx, p_polygon);
undo_redo->add_undo_method(navpoly.ptr(), "set_outline", p_idx, p_previous);
- undo_redo->add_do_method(navpoly.ptr(), "make_polygons_from_outlines");
- undo_redo->add_undo_method(navpoly.ptr(), "make_polygons_from_outlines");
}
bool NavigationPolygonEditor::_has_resource() const {
@@ -119,7 +114,84 @@ void NavigationPolygonEditor::_create_resource() {
_menu_option(MODE_CREATE);
}
-NavigationPolygonEditor::NavigationPolygonEditor() {}
+NavigationPolygonEditor::NavigationPolygonEditor() {
+ bake_hbox = memnew(HBoxContainer);
+ add_child(bake_hbox);
+
+ button_bake = memnew(Button);
+ button_bake->set_flat(true);
+ bake_hbox->add_child(button_bake);
+ button_bake->set_toggle_mode(true);
+ button_bake->set_text(TTR("Bake NavigationPolygon"));
+ button_bake->set_tooltip_text(TTR("Bakes the NavigationPolygon by first parsing the scene for source geometry and then creating the navigation polygon vertices and polygons."));
+ button_bake->connect("pressed", callable_mp(this, &NavigationPolygonEditor::_bake_pressed));
+
+ button_reset = memnew(Button);
+ button_reset->set_flat(true);
+ bake_hbox->add_child(button_reset);
+ button_reset->set_text(TTR("Clear NavigationPolygon"));
+ button_reset->set_tooltip_text(TTR("Clears the internal NavigationPolygon outlines, vertices and polygons."));
+ button_reset->connect("pressed", callable_mp(this, &NavigationPolygonEditor::_clear_pressed));
+
+ bake_info = memnew(Label);
+ bake_hbox->add_child(bake_info);
+
+ err_dialog = memnew(AcceptDialog);
+ add_child(err_dialog);
+ node = nullptr;
+}
+
+void NavigationPolygonEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ button_bake->set_icon(get_theme_icon(SNAME("Bake"), SNAME("EditorIcons")));
+ button_reset->set_icon(get_theme_icon(SNAME("Reload"), SNAME("EditorIcons")));
+ } break;
+ }
+}
+
+void NavigationPolygonEditor::_bake_pressed() {
+ button_bake->set_pressed(false);
+
+ ERR_FAIL_NULL(node);
+ Ref<NavigationPolygon> navigation_polygon = node->get_navigation_polygon();
+ if (!navigation_polygon.is_valid()) {
+ err_dialog->set_text(TTR("A NavigationPolygon resource must be set or created for this node to work."));
+ err_dialog->popup_centered();
+ return;
+ }
+
+ node->bake_navigation_polygon(true);
+
+ node->queue_redraw();
+}
+
+void NavigationPolygonEditor::_clear_pressed() {
+ if (node) {
+ if (node->get_navigation_polygon().is_valid()) {
+ node->get_navigation_polygon()->clear();
+ }
+ }
+
+ button_bake->set_pressed(false);
+ bake_info->set_text("");
+
+ if (node) {
+ node->queue_redraw();
+ }
+}
+
+void NavigationPolygonEditor::_update_polygon_editing_state() {
+ if (!_get_node()) {
+ return;
+ }
+
+ if (node != nullptr && node->get_navigation_polygon().is_valid()) {
+ bake_hbox->show();
+ } else {
+ bake_hbox->hide();
+ }
+}
NavigationPolygonEditorPlugin::NavigationPolygonEditorPlugin() :
AbstractPolygon2DEditorPlugin(memnew(NavigationPolygonEditor), "NavigationRegion2D") {
diff --git a/editor/plugins/navigation_polygon_editor_plugin.h b/editor/plugins/navigation_polygon_editor_plugin.h
index f43c052dd3..f1d0cd8751 100644
--- a/editor/plugins/navigation_polygon_editor_plugin.h
+++ b/editor/plugins/navigation_polygon_editor_plugin.h
@@ -32,16 +32,38 @@
#define NAVIGATION_POLYGON_EDITOR_PLUGIN_H
#include "editor/plugins/abstract_polygon_2d_editor.h"
-#include "scene/2d/navigation_region_2d.h"
+
+#include "editor/editor_plugin.h"
+
+class AcceptDialog;
+class HBoxContainer;
+class NavigationPolygon;
+class NavigationRegion2D;
class NavigationPolygonEditor : public AbstractPolygon2DEditor {
+ friend class NavigationPolygonEditorPlugin;
+
GDCLASS(NavigationPolygonEditor, AbstractPolygon2DEditor);
NavigationRegion2D *node = nullptr;
Ref<NavigationPolygon> _ensure_navpoly() const;
+ AcceptDialog *err_dialog = nullptr;
+
+ HBoxContainer *bake_hbox = nullptr;
+ Button *button_bake = nullptr;
+ Button *button_reset = nullptr;
+ Label *bake_info = nullptr;
+
+ void _bake_pressed();
+ void _clear_pressed();
+
+ void _update_polygon_editing_state();
+
protected:
+ void _notification(int p_what);
+
virtual Node2D *_get_node() const override;
virtual void _set_node(Node *p_polygon) override;
@@ -63,6 +85,8 @@ public:
class NavigationPolygonEditorPlugin : public AbstractPolygon2DEditorPlugin {
GDCLASS(NavigationPolygonEditorPlugin, AbstractPolygon2DEditorPlugin);
+ NavigationPolygonEditor *navigation_polygon_editor = nullptr;
+
public:
NavigationPolygonEditorPlugin();
};
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index d5ad21e346..dc3f88705b 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -1306,6 +1306,9 @@ void ScriptTextEditor::_edit_option(int p_op) {
case EDIT_DUPLICATE_SELECTION: {
code_editor->duplicate_selection();
} break;
+ case EDIT_DUPLICATE_LINES: {
+ code_editor->get_text_editor()->duplicate_lines();
+ } break;
case EDIT_TOGGLE_FOLD_LINE: {
int previous_line = -1;
for (int caret_idx : tx->get_caret_index_edit_order()) {
@@ -2173,6 +2176,7 @@ void ScriptTextEditor::_enable_code_editor() {
edit_menu->get_popup()->add_separator();
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_lines"), EDIT_DUPLICATE_LINES);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/evaluate_selection"), EDIT_EVALUATE);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_word_wrap"), EDIT_TOGGLE_WORD_WRAP);
edit_menu->get_popup()->add_separator();
@@ -2395,6 +2399,8 @@ void ScriptTextEditor::register_editor() {
ED_SHORTCUT("script_text_editor/unfold_all_lines", TTR("Unfold All Lines"), Key::NONE);
ED_SHORTCUT("script_text_editor/duplicate_selection", TTR("Duplicate Selection"), KeyModifierMask::SHIFT | KeyModifierMask::CTRL | Key::D);
ED_SHORTCUT_OVERRIDE("script_text_editor/duplicate_selection", "macos", KeyModifierMask::SHIFT | KeyModifierMask::META | Key::C);
+ ED_SHORTCUT("script_text_editor/duplicate_lines", TTR("Duplicate Lines"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::DOWN);
+ ED_SHORTCUT_OVERRIDE("script_text_editor/duplicate_lines", "macos", KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::DOWN);
ED_SHORTCUT("script_text_editor/evaluate_selection", TTR("Evaluate Selection"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::E);
ED_SHORTCUT("script_text_editor/toggle_word_wrap", TTR("Toggle Word Wrap"), KeyModifierMask::ALT | Key::Z);
ED_SHORTCUT("script_text_editor/trim_trailing_whitespace", TTR("Trim Trailing Whitespace"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::T);
diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h
index 0efe7d54e3..8c0ba6d7e6 100644
--- a/editor/plugins/script_text_editor.h
+++ b/editor/plugins/script_text_editor.h
@@ -126,6 +126,7 @@ class ScriptTextEditor : public ScriptEditorBase {
EDIT_UNINDENT,
EDIT_DELETE_LINE,
EDIT_DUPLICATE_SELECTION,
+ EDIT_DUPLICATE_LINES,
EDIT_PICK_COLOR,
EDIT_TO_UPPERCASE,
EDIT_TO_LOWERCASE,
diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp
index 92ee468bc2..b0a69cfb5c 100644
--- a/editor/plugins/text_editor.cpp
+++ b/editor/plugins/text_editor.cpp
@@ -392,6 +392,9 @@ void TextEditor::_edit_option(int p_op) {
case EDIT_DUPLICATE_SELECTION: {
code_editor->duplicate_selection();
} break;
+ case EDIT_DUPLICATE_LINES: {
+ code_editor->get_text_editor()->duplicate_lines();
+ } break;
case EDIT_TOGGLE_FOLD_LINE: {
int previous_line = -1;
for (int caret_idx : tx->get_caret_index_edit_order()) {
@@ -651,6 +654,7 @@ TextEditor::TextEditor() {
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unfold_all_lines"), EDIT_UNFOLD_ALL_LINES);
edit_menu->get_popup()->add_separator();
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_lines"), EDIT_DUPLICATE_LINES);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_word_wrap"), EDIT_TOGGLE_WORD_WRAP);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/trim_trailing_whitespace"), EDIT_TRIM_TRAILING_WHITESAPCE);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_spaces"), EDIT_CONVERT_INDENT_TO_SPACES);
diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h
index d1aa2a9047..a4ec2d882b 100644
--- a/editor/plugins/text_editor.h
+++ b/editor/plugins/text_editor.h
@@ -71,6 +71,7 @@ private:
EDIT_UNINDENT,
EDIT_DELETE_LINE,
EDIT_DUPLICATE_SELECTION,
+ EDIT_DUPLICATE_LINES,
EDIT_TO_UPPERCASE,
EDIT_TO_LOWERCASE,
EDIT_CAPITALIZE,
diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp
index b68b283a61..27f42608c6 100644
--- a/editor/plugins/text_shader_editor.cpp
+++ b/editor/plugins/text_shader_editor.cpp
@@ -671,6 +671,9 @@ void TextShaderEditor::_menu_option(int p_option) {
case EDIT_DUPLICATE_SELECTION: {
shader_editor->duplicate_selection();
} break;
+ case EDIT_DUPLICATE_LINES: {
+ shader_editor->get_text_editor()->duplicate_lines();
+ } break;
case EDIT_TOGGLE_WORD_WRAP: {
TextEdit::LineWrappingMode wrap = shader_editor->get_text_editor()->get_line_wrapping_mode();
shader_editor->get_text_editor()->set_line_wrapping_mode(wrap == TextEdit::LINE_WRAPPING_BOUNDARY ? TextEdit::LINE_WRAPPING_NONE : TextEdit::LINE_WRAPPING_BOUNDARY);
@@ -1122,6 +1125,7 @@ TextShaderEditor::TextShaderEditor() {
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_lines"), EDIT_DUPLICATE_LINES);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_word_wrap"), EDIT_TOGGLE_WORD_WRAP);
edit_menu->get_popup()->add_separator();
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query"), EDIT_COMPLETE);
diff --git a/editor/plugins/text_shader_editor.h b/editor/plugins/text_shader_editor.h
index 03827f1b83..eba2ec0bb9 100644
--- a/editor/plugins/text_shader_editor.h
+++ b/editor/plugins/text_shader_editor.h
@@ -120,6 +120,7 @@ class TextShaderEditor : public MarginContainer {
EDIT_UNINDENT,
EDIT_DELETE_LINE,
EDIT_DUPLICATE_SELECTION,
+ EDIT_DUPLICATE_LINES,
EDIT_TOGGLE_WORD_WRAP,
EDIT_TOGGLE_COMMENT,
EDIT_COMPLETE,
diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp
index d4e1048642..0472f48c62 100644
--- a/editor/script_create_dialog.cpp
+++ b/editor/script_create_dialog.cpp
@@ -526,12 +526,6 @@ void ScriptCreateDialog::_path_changed(const String &p_path) {
validation_panel->update();
}
-void ScriptCreateDialog::_path_submitted(const String &p_path) {
- if (!get_ok_button()->is_disabled()) {
- ok_pressed();
- }
-}
-
void ScriptCreateDialog::_update_template_menu() {
bool is_language_using_templates = language->is_using_templates();
template_menu->set_disabled(false);
@@ -960,6 +954,7 @@ ScriptCreateDialog::ScriptCreateDialog() {
file_path->connect("text_changed", callable_mp(this, &ScriptCreateDialog::_path_changed));
file_path->set_h_size_flags(Control::SIZE_EXPAND_FILL);
hb->add_child(file_path);
+ register_text_enter(file_path);
path_button = memnew(Button);
path_button->connect("pressed", callable_mp(this, &ScriptCreateDialog::_browse_path).bind(false, true));
hb->add_child(path_button);
@@ -973,7 +968,7 @@ ScriptCreateDialog::ScriptCreateDialog() {
built_in_name = memnew(LineEdit);
built_in_name->set_h_size_flags(Control::SIZE_EXPAND_FILL);
- built_in_name->connect("text_submitted", callable_mp(this, &ScriptCreateDialog::_path_submitted));
+ register_text_enter(built_in_name);
label = memnew(Label(TTR("Name:")));
gc->add_child(label);
gc->add_child(built_in_name);
diff --git a/editor/script_create_dialog.h b/editor/script_create_dialog.h
index a4ff602eec..66301ea3b9 100644
--- a/editor/script_create_dialog.h
+++ b/editor/script_create_dialog.h
@@ -97,7 +97,6 @@ class ScriptCreateDialog : public ConfirmationDialog {
void _path_hbox_sorted();
bool _can_be_built_in();
void _path_changed(const String &p_path = String());
- void _path_submitted(const String &p_path = String());
void _language_changed(int l = 0);
void _built_in_pressed();
void _use_template_pressed();