diff options
Diffstat (limited to 'editor')
36 files changed, 2223 insertions, 451 deletions
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 9ae9ab1501..4c7f2f53cc 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -212,35 +212,39 @@ void FindReplaceBar::_replace_all() { text_edit->begin_complex_operation(); - while (search_next()) { - - // replace area - Point2i match_from(result_line, result_col); - Point2i match_to(result_line, result_col + search_text_len); - - if (match_from < prev_match) - break; // done + if (search_current()) { + do { + // replace area + Point2i match_from(result_line, result_col); + Point2i match_to(result_line, result_col + search_text_len); + + if (match_from < prev_match) { + break; // done + } - prev_match = Point2i(result_line, result_col + replace_text.length()); + prev_match = Point2i(result_line, result_col + replace_text.length()); - text_edit->unfold_line(result_line); - text_edit->select(result_line, result_col, result_line, match_to.y); + text_edit->unfold_line(result_line); + text_edit->select(result_line, result_col, result_line, match_to.y); - if (selection_enabled && is_selection_only()) { + if (selection_enabled && is_selection_only()) { + if (match_from < selection_begin || match_to > selection_end) { + continue; + } - if (match_from < selection_begin || match_to > selection_end) - continue; + // replace but adjust selection bounds + text_edit->insert_text_at_cursor(replace_text); + if (match_to.x == selection_end.x) { + selection_end.y += replace_text.length() - search_text_len; + } - // replace but adjust selection bounds - text_edit->insert_text_at_cursor(replace_text); - if (match_to.x == selection_end.x) - selection_end.y += replace_text.length() - search_text_len; - } else { - // just replace - text_edit->insert_text_at_cursor(replace_text); - } + } else { + // just replace + text_edit->insert_text_at_cursor(replace_text); + } - rc++; + rc++; + } while (search_next()); } text_edit->end_complex_operation(); diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp index 13ef6e9e0e..78fb35e354 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -433,26 +433,7 @@ Object *CreateDialog::instance_selected() { custom = md; if (custom != String()) { - if (EditorNode::get_editor_data().get_custom_types().has(custom)) { - - for (int i = 0; i < EditorNode::get_editor_data().get_custom_types()[custom].size(); i++) { - if (EditorNode::get_editor_data().get_custom_types()[custom][i].name == selected->get_text(0)) { - Ref<Texture> icon = EditorNode::get_editor_data().get_custom_types()[custom][i].icon; - Ref<Script> script = EditorNode::get_editor_data().get_custom_types()[custom][i].script; - String name = selected->get_text(0); - - Object *ob = ClassDB::instance(custom); - ERR_FAIL_COND_V(!ob, NULL); - if (ob->is_class("Node")) { - ob->call("set_name", name); - } - ob->set_script(script.get_ref_ptr()); - if (icon.is_valid()) - ob->set_meta("_editor_icon", icon); - return ob; - } - } - } + return EditorNode::get_editor_data().instance_custom_type(selected->get_text(0), custom); } else { return ClassDB::instance(selected->get_text(0)); } diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index 95ed40d889..ef9265ecd2 100644 --- a/editor/editor_data.cpp +++ b/editor/editor_data.cpp @@ -452,6 +452,31 @@ void EditorData::add_custom_type(const String &p_type, const String &p_inherits, custom_types[p_inherits].push_back(ct); } +Object *EditorData::instance_custom_type(const String &p_type, const String &p_inherits) { + + if (get_custom_types().has(p_inherits)) { + + for (int i = 0; i < get_custom_types()[p_inherits].size(); i++) { + if (get_custom_types()[p_inherits][i].name == p_type) { + Ref<Texture> icon = get_custom_types()[p_inherits][i].icon; + Ref<Script> script = get_custom_types()[p_inherits][i].script; + + Object *ob = ClassDB::instance(p_inherits); + ERR_FAIL_COND_V(!ob, NULL); + if (ob->is_class("Node")) { + ob->call("set_name", p_type); + } + ob->set_script(script.get_ref_ptr()); + if (icon.is_valid()) + ob->set_meta("_editor_icon", icon); + return ob; + } + } + } + + return NULL; +} + void EditorData::remove_custom_type(const String &p_type) { for (Map<String, Vector<CustomType> >::Element *E = custom_types.front(); E; E = E->next()) { diff --git a/editor/editor_data.h b/editor/editor_data.h index 844145853d..1a498a6150 100644 --- a/editor/editor_data.h +++ b/editor/editor_data.h @@ -171,6 +171,7 @@ public: void restore_editor_global_states(); void add_custom_type(const String &p_type, const String &p_inherits, const Ref<Script> &p_script, const Ref<Texture> &p_icon); + Object *instance_custom_type(const String &p_type, const String &p_inherits); void remove_custom_type(const String &p_type); const Map<String, Vector<CustomType> > &get_custom_types() const { return custom_types; } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 0bca78b493..c31bec9a1b 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1234,29 +1234,25 @@ void EditorNode::_dialog_action(String p_file) { } break; case FILE_EXPORT_TILESET: { - Ref<TileSet> ml; - if (FileAccess::exists(p_file)) { - ml = ResourceLoader::load(p_file, "TileSet"); + Ref<TileSet> tileset; + if (FileAccess::exists(p_file) && file_export_lib_merge->is_pressed()) { + tileset = ResourceLoader::load(p_file, "TileSet"); - if (ml.is_null()) { - if (file_export_lib_merge->is_pressed()) { - current_option = -1; - accept->get_ok()->set_text(TTR("I see..")); - accept->set_text(TTR("Can't load TileSet for merging!")); - accept->popup_centered_minsize(); - return; - } - } else if (!file_export_lib_merge->is_pressed()) { - ml->clear(); + if (tileset.is_null()) { + current_option = -1; + accept->get_ok()->set_text(TTR("I see..")); + accept->set_text(TTR("Can't load TileSet for merging!")); + accept->popup_centered_minsize(); + return; } } else { - ml = Ref<TileSet>(memnew(TileSet)); + tileset = Ref<TileSet>(memnew(TileSet)); } - TileSetEditor::update_library_file(editor_data.get_edited_scene_root(), ml, true); + TileSetEditor::update_library_file(editor_data.get_edited_scene_root(), tileset, true); - Error err = ResourceSaver::save(p_file, ml); + Error err = ResourceSaver::save(p_file, tileset); if (err) { current_option = -1; @@ -3896,6 +3892,53 @@ void EditorNode::_update_dock_slots_visibility() { } } +void EditorNode::_dock_tab_changed(int p_tab) { + + // update visibility but dont set current tab + VSplitContainer *splits[DOCK_SLOT_MAX / 2] = { + left_l_vsplit, + left_r_vsplit, + right_l_vsplit, + right_r_vsplit, + }; + + if (!docks_visible) { + + for (int i = 0; i < DOCK_SLOT_MAX; i++) { + dock_slot[i]->hide(); + } + + for (int i = 0; i < DOCK_SLOT_MAX / 2; i++) { + splits[i]->hide(); + } + + right_hsplit->hide(); + bottom_panel->hide(); + } else { + for (int i = 0; i < DOCK_SLOT_MAX; i++) { + + if (dock_slot[i]->get_tab_count()) + dock_slot[i]->show(); + else + dock_slot[i]->hide(); + } + + for (int i = 0; i < DOCK_SLOT_MAX / 2; i++) { + bool in_use = dock_slot[i * 2 + 0]->get_tab_count() || dock_slot[i * 2 + 1]->get_tab_count(); + if (in_use) + splits[i]->show(); + else + splits[i]->hide(); + } + bottom_panel->show(); + + if (right_l_vsplit->is_visible() || right_r_vsplit->is_visible()) + right_hsplit->show(); + else + right_hsplit->hide(); + } +} + void EditorNode::_load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section) { for (int i = 0; i < DOCK_SLOT_MAX; i++) { @@ -4766,6 +4809,7 @@ void EditorNode::_bind_methods() { ClassDB::bind_method("_dock_popup_exit", &EditorNode::_dock_popup_exit); ClassDB::bind_method("_dock_move_left", &EditorNode::_dock_move_left); ClassDB::bind_method("_dock_move_right", &EditorNode::_dock_move_right); + ClassDB::bind_method("_dock_tab_changed", &EditorNode::_dock_tab_changed); ClassDB::bind_method("_layout_menu_option", &EditorNode::_layout_menu_option); @@ -5125,6 +5169,9 @@ EditorNode::EditorNode() { dock_slot[i]->set_popup(dock_select_popup); dock_slot[i]->connect("pre_popup_pressed", this, "_dock_pre_popup", varray(i)); dock_slot[i]->set_tab_align(TabContainer::ALIGN_LEFT); + dock_slot[i]->set_drag_to_rearrange_enabled(true); + dock_slot[i]->set_tabs_rearrange_group(1); + dock_slot[i]->connect("tab_changed", this, "_dock_tab_changed"); } dock_drag_timer = memnew(Timer); @@ -5162,6 +5209,7 @@ EditorNode::EditorNode() { scene_tabs->set_tab_align(Tabs::ALIGN_LEFT); scene_tabs->set_tab_close_display_policy((bool(EDITOR_DEF("interface/scene_tabs/always_show_close_button", false)) ? Tabs::CLOSE_BUTTON_SHOW_ALWAYS : Tabs::CLOSE_BUTTON_SHOW_ACTIVE_ONLY)); scene_tabs->set_min_width(int(EDITOR_DEF("interface/scene_tabs/minimum_width", 50)) * EDSCALE); + scene_tabs->set_drag_to_rearrange_enabled(true); scene_tabs->connect("tab_changed", this, "_scene_tab_changed"); scene_tabs->connect("right_button_pressed", this, "_scene_tab_script_edited"); scene_tabs->connect("tab_close", this, "_scene_tab_closed"); diff --git a/editor/editor_node.h b/editor/editor_node.h index 90bebffca6..f774fa0a2e 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -569,6 +569,7 @@ private: void _save_docks_to_config(Ref<ConfigFile> p_layout, const String &p_section); void _load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section); void _update_dock_slots_visibility(); + void _dock_tab_changed(int p_tab); bool restoring_scenes; void _save_open_scenes_to_config(Ref<ConfigFile> p_layout, const String &p_section); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index b10f785a28..3582379e34 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -626,8 +626,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_color("font_color_disabled", "PopupMenu", font_color_disabled); theme->set_icon("checked", "PopupMenu", theme->get_icon("GuiChecked", "EditorIcons")); theme->set_icon("unchecked", "PopupMenu", theme->get_icon("GuiUnchecked", "EditorIcons")); - theme->set_icon("radio_checked", "PopupMenu", theme->get_icon("GuiChecked", "EditorIcons")); - theme->set_icon("radio_unchecked", "PopupMenu", theme->get_icon("GuiUnchecked", "EditorIcons")); + theme->set_icon("radio_checked", "PopupMenu", theme->get_icon("GuiRadioChecked", "EditorIcons")); + theme->set_icon("radio_unchecked", "PopupMenu", theme->get_icon("GuiRadioUnchecked", "EditorIcons")); theme->set_icon("submenu", "PopupMenu", theme->get_icon("ArrowRight", "EditorIcons")); theme->set_icon("visibility_hidden", "PopupMenu", theme->get_icon("GuiVisibilityHidden", "EditorIcons")); theme->set_icon("visibility_visible", "PopupMenu", theme->get_icon("GuiVisibilityVisible", "EditorIcons")); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index d5783c9b1a..d4c7d7483e 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -533,6 +533,7 @@ void FileSystemDock::_update_files(bool p_keep_selection) { filelist.push_back(fi); } + filelist.sort(); } String oi = "Object"; @@ -719,12 +720,13 @@ void FileSystemDock::_push_to_history() { button_hist_next->set_disabled(history_pos == history.size() - 1); } -void FileSystemDock::_get_all_files_in_dir(EditorFileSystemDirectory *efsd, Vector<String> &files) const { +void FileSystemDock::_get_all_items_in_dir(EditorFileSystemDirectory *efsd, Vector<String> &files, Vector<String> &folders) const { if (efsd == NULL) return; for (int i = 0; i < efsd->get_subdir_count(); i++) { - _get_all_files_in_dir(efsd->get_subdir(i), files); + folders.push_back(efsd->get_subdir(i)->get_path()); + _get_all_items_in_dir(efsd->get_subdir(i), files, folders); } for (int i = 0; i < efsd->get_file_count(); i++) { files.push_back(efsd->get_file_path(i)); @@ -746,7 +748,8 @@ void FileSystemDock::_find_remaps(EditorFileSystemDirectory *efsd, const Map<Str } } -void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_new_path, Map<String, String> &p_renames) const { +void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_new_path, + Map<String, String> &p_file_renames, Map<String, String> &p_folder_renames) const { //Ensure folder paths end with "/" String old_path = (p_item.is_file || p_item.path.ends_with("/")) ? p_item.path : (p_item.path + "/"); String new_path = (p_item.is_file || p_new_path.ends_with("/")) ? p_new_path : (p_new_path + "/"); @@ -763,11 +766,13 @@ void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_ } //Build a list of files which will have new paths as a result of this operation - Vector<String> changed_paths; + Vector<String> file_changed_paths; + Vector<String> folder_changed_paths; if (p_item.is_file) { - changed_paths.push_back(old_path); + file_changed_paths.push_back(old_path); } else { - _get_all_files_in_dir(EditorFileSystem::get_singleton()->get_filesystem_path(old_path), changed_paths); + folder_changed_paths.push_back(old_path); + _get_all_items_in_dir(EditorFileSystem::get_singleton()->get_filesystem_path(old_path), file_changed_paths, folder_changed_paths); } DirAccess *da = DirAccess::create(DirAccess::ACCESS_RESOURCES); @@ -783,12 +788,12 @@ void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_ } // update scene if it is open - for (int i = 0; i < changed_paths.size(); ++i) { - String new_item_path = p_item.is_file ? new_path : changed_paths[i].replace_first(old_path, new_path); - if (ResourceLoader::get_resource_type(new_item_path) == "PackedScene" && editor->is_scene_open(changed_paths[i])) { + for (int i = 0; i < file_changed_paths.size(); ++i) { + String new_item_path = p_item.is_file ? new_path : file_changed_paths[i].replace_first(old_path, new_path); + if (ResourceLoader::get_resource_type(new_item_path) == "PackedScene" && editor->is_scene_open(file_changed_paths[i])) { EditorData *ed = &editor->get_editor_data(); for (int j = 0; j < ed->get_edited_scene_count(); j++) { - if (ed->get_scene_path(j) == changed_paths[i]) { + if (ed->get_scene_path(j) == file_changed_paths[i]) { ed->get_edited_scene_root(j)->set_filename(new_item_path); break; } @@ -797,9 +802,12 @@ void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_ } //Only treat as a changed dependency if it was successfully moved - for (int i = 0; i < changed_paths.size(); ++i) { - p_renames[changed_paths[i]] = changed_paths[i].replace_first(old_path, new_path); - print_line(" Remap: " + changed_paths[i] + " -> " + p_renames[changed_paths[i]]); + for (int i = 0; i < file_changed_paths.size(); ++i) { + p_file_renames[file_changed_paths[i]] = file_changed_paths[i].replace_first(old_path, new_path); + print_line(" Remap: " + file_changed_paths[i] + " -> " + p_file_renames[file_changed_paths[i]]); + } + for (int i = 0; i < folder_changed_paths.size(); ++i) { + p_folder_renames[folder_changed_paths[i]] = folder_changed_paths[i].replace_first(old_path, new_path); } } else { EditorNode::get_singleton()->add_io_error(TTR("Error moving:") + "\n" + old_path + "\n"); @@ -912,6 +920,25 @@ void FileSystemDock::_update_dependencies_after_move(const Map<String, String> & } } +void FileSystemDock::_update_favorite_dirs_list_after_move(const Map<String, String> &p_renames) const { + + Vector<String> favorite_dirs = EditorSettings::get_singleton()->get_favorite_dirs(); + Vector<String> new_favorite_dirs; + + for (int i = 0; i < favorite_dirs.size(); i++) { + String old_path = favorite_dirs[i] + "/"; + + if (p_renames.has(old_path)) { + String new_path = p_renames[old_path]; + new_favorite_dirs.push_back(new_path.substr(0, new_path.length() - 1)); + } else { + new_favorite_dirs.push_back(favorite_dirs[i]); + } + } + + EditorSettings::get_singleton()->set_favorite_dirs(new_favorite_dirs); +} + void FileSystemDock::_make_dir_confirm() { String dir_name = make_dir_dialog_text->get_text().strip_edges(); @@ -970,10 +997,12 @@ void FileSystemDock::_rename_operation_confirm() { } memdelete(da); - Map<String, String> renames; - _try_move_item(to_rename, new_path, renames); - _update_dependencies_after_move(renames); - _update_resource_paths_after_move(renames); + Map<String, String> file_renames; + Map<String, String> folder_renames; + _try_move_item(to_rename, new_path, file_renames, folder_renames); + _update_dependencies_after_move(file_renames); + _update_resource_paths_after_move(file_renames); + _update_favorite_dirs_list_after_move(folder_renames); //Rescan everything print_line("call rescan!"); @@ -1017,15 +1046,17 @@ void FileSystemDock::_duplicate_operation_confirm() { void FileSystemDock::_move_operation_confirm(const String &p_to_path) { - Map<String, String> renames; + Map<String, String> file_renames; + Map<String, String> folder_renames; for (int i = 0; i < to_move.size(); i++) { String old_path = to_move[i].path.ends_with("/") ? to_move[i].path.substr(0, to_move[i].path.length() - 1) : to_move[i].path; String new_path = p_to_path.plus_file(old_path.get_file()); - _try_move_item(to_move[i], new_path, renames); + _try_move_item(to_move[i], new_path, file_renames, folder_renames); } - _update_dependencies_after_move(renames); - _update_resource_paths_after_move(renames); + _update_dependencies_after_move(file_renames); + _update_resource_paths_after_move(file_renames); + _update_favorite_dirs_list_after_move(folder_renames); print_line("call rescan!"); _rescan(); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index 377316d1ba..c8448a1022 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -176,12 +176,13 @@ private: void _file_selected(); void _dir_selected(); - void _get_all_files_in_dir(EditorFileSystemDirectory *efsd, Vector<String> &files) const; + void _get_all_items_in_dir(EditorFileSystemDirectory *efsd, Vector<String> &files, Vector<String> &folders) const; void _find_remaps(EditorFileSystemDirectory *efsd, const Map<String, String> &renames, Vector<String> &to_remaps) const; - void _try_move_item(const FileOrFolder &p_item, const String &p_new_path, Map<String, String> &p_renames) const; + void _try_move_item(const FileOrFolder &p_item, const String &p_new_path, Map<String, String> &p_file_renames, Map<String, String> &p_folder_renames) const; void _try_duplicate_item(const FileOrFolder &p_item, const String &p_new_path) const; void _update_dependencies_after_move(const Map<String, String> &p_renames) const; void _update_resource_paths_after_move(const Map<String, String> &p_renames) const; + void _update_favorite_dirs_list_after_move(const Map<String, String> &p_renames) const; void _make_dir_confirm(); void _rename_operation_confirm(); diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp new file mode 100644 index 0000000000..9442bbc0e8 --- /dev/null +++ b/editor/find_in_files.cpp @@ -0,0 +1,829 @@ +/*************************************************************************/ +/* find_in_files.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 "find_in_files.h" +#include "core/os/dir_access.h" +#include "core/os/os.h" +#include "editor_scale.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/check_box.h" +#include "scene/gui/file_dialog.h" +#include "scene/gui/grid_container.h" +#include "scene/gui/item_list.h" +#include "scene/gui/label.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/progress_bar.h" + +#define ROOT_PREFIX "res://" + +const char *FindInFiles::SIGNAL_RESULT_FOUND = "result_found"; +const char *FindInFiles::SIGNAL_FINISHED = "finished"; + +// TODO Would be nice in Vector and PoolVectors +template <typename T> +inline void pop_back(T &container) { + container.resize(container.size() - 1); +} + +// TODO Copied from TextEdit private, would be nice to extract it in a single place +static bool is_text_char(CharType c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; +} + +FindInFiles::FindInFiles() { + _root_prefix = ROOT_PREFIX; + _extension_filter.insert("gd"); + _extension_filter.insert("cs"); + _searching = false; + _whole_words = true; + _match_case = true; +} + +void FindInFiles::set_search_text(String p_pattern) { + _pattern = p_pattern; +} + +void FindInFiles::set_whole_words(bool p_whole_word) { + _whole_words = p_whole_word; +} + +void FindInFiles::set_match_case(bool p_match_case) { + _match_case = p_match_case; +} + +void FindInFiles::set_folder(String folder) { + _root_dir = folder; +} + +void FindInFiles::set_filter(const Set<String> &exts) { + _extension_filter = exts; +} + +void FindInFiles::_notification(int p_notification) { + if (p_notification == NOTIFICATION_PROCESS) { + _process(); + } +} + +void FindInFiles::start() { + if (_pattern == "") { + print_line("Nothing to search, pattern is empty"); + emit_signal(SIGNAL_FINISHED); + return; + } + if (_extension_filter.size() == 0) { + print_line("Nothing to search, filter matches no files"); + emit_signal(SIGNAL_FINISHED); + return; + } + + // Init search + _current_dir = ""; + PoolStringArray init_folder; + init_folder.append(_root_dir); + _folders_stack.push_back(init_folder); + + _initial_files_count = 0; + + _searching = true; + set_process(true); +} + +void FindInFiles::stop() { + _searching = false; + _current_dir = ""; + set_process(false); +} + +void FindInFiles::_process() { + // This part can be moved to a thread if needed + + OS &os = *OS::get_singleton(); + float duration = 0.0; + while (duration < 1.0 / 120.0) { + float time_before = os.get_ticks_msec(); + _iterate(); + duration += (os.get_ticks_msec() - time_before); + } +} + +void FindInFiles::_iterate() { + + if (_folders_stack.size() != 0) { + + // Scan folders first so we can build a list of files and have progress info later + + PoolStringArray &folders_to_scan = _folders_stack[_folders_stack.size() - 1]; + + if (folders_to_scan.size() != 0) { + // Scan one folder below + + String folder_name = folders_to_scan[folders_to_scan.size() - 1]; + pop_back(folders_to_scan); + + _current_dir = _current_dir.plus_file(folder_name); + + PoolStringArray sub_dirs; + _scan_dir(_root_prefix + _current_dir, sub_dirs); + + if (sub_dirs.size() != 0) { + _folders_stack.push_back(sub_dirs); + } + + } else { + // Go back one level + + pop_back(_folders_stack); + _current_dir = _current_dir.get_base_dir(); + + if (_folders_stack.size() == 0) { + // All folders scanned + _initial_files_count = _files_to_scan.size(); + } + } + + } else if (_files_to_scan.size() != 0) { + + // Then scan files + + String fpath = _files_to_scan[_files_to_scan.size() - 1]; + pop_back(_files_to_scan); + _scan_file(_root_prefix + fpath); + + } else { + print_line("Search complete"); + set_process(false); + _current_dir = ""; + _searching = false; + emit_signal(SIGNAL_FINISHED); + } +} + +float FindInFiles::get_progress() const { + if (_initial_files_count != 0) { + return static_cast<float>(_initial_files_count - _files_to_scan.size()) / static_cast<float>(_initial_files_count); + } + return 0; +} + +void FindInFiles::_scan_dir(String path, PoolStringArray &out_folders) { + + DirAccess *dir = DirAccess::open(path); + if (dir == NULL) { + print_line("Cannot open directory! " + path); + return; + } + + //print_line(String("Scanning ") + path); + + dir->list_dir_begin(); + + for (int i = 0; i < 1000; ++i) { + String file = dir->get_next(); + + if (file == "") + break; + + // Ignore special dirs and hidden dirs (such as .git and .import) + if (file == "." || file == ".." || file.begins_with(".")) + continue; + + if (dir->current_is_dir()) + out_folders.append(file); + + else { + String file_ext = file.get_extension(); + if (_extension_filter.has(file_ext)) { + _files_to_scan.push_back(file); + } + } + } +} + +void FindInFiles::_scan_file(String fpath) { + + FileAccess *f = FileAccess::open(fpath, FileAccess::READ); + if (f == NULL) { + f->close(); + print_line(String("Cannot open file ") + fpath); + return; + } + + int line_number = 0; + + while (!f->eof_reached()) { + + // line number starts at 1 + ++line_number; + + int begin = 0; + int end = 0; + + String line = f->get_line(); + + // Find all occurrences in the current line + while (true) { + begin = _match_case ? line.find(_pattern, end) : line.findn(_pattern, end); + + if (begin == -1) + break; + + end = begin + _pattern.length(); + + if (_whole_words) { + if (begin > 0 && is_text_char(line[begin - 1])) { + continue; + } + if (end < line.size() && is_text_char(line[end])) { + continue; + } + } + + emit_signal(SIGNAL_RESULT_FOUND, fpath, line_number, begin, end, line); + } + } + + f->close(); +} + +void FindInFiles::_bind_methods() { + + ADD_SIGNAL(MethodInfo(SIGNAL_RESULT_FOUND, + PropertyInfo(Variant::STRING, "path"), + PropertyInfo(Variant::INT, "line_number"), + PropertyInfo(Variant::INT, "begin"), + PropertyInfo(Variant::INT, "end"), + PropertyInfo(Variant::STRING, "text"))); + + ADD_SIGNAL(MethodInfo(SIGNAL_FINISHED)); +} + +//----------------------------------------------------------------------------- +const char *FindInFilesDialog::SIGNAL_FIND_REQUESTED = "find_requested"; +const char *FindInFilesDialog::SIGNAL_REPLACE_REQUESTED = "replace_requested"; + +FindInFilesDialog::FindInFilesDialog() { + + set_custom_minimum_size(Size2(400, 190)); + set_resizable(true); + set_title(TTR("Find in files")); + + VBoxContainer *vbc = memnew(VBoxContainer); + vbc->set_anchor_and_margin(MARGIN_LEFT, ANCHOR_BEGIN, 8 * EDSCALE); + vbc->set_anchor_and_margin(MARGIN_TOP, ANCHOR_BEGIN, 8 * EDSCALE); + vbc->set_anchor_and_margin(MARGIN_RIGHT, ANCHOR_END, -8 * EDSCALE); + vbc->set_anchor_and_margin(MARGIN_BOTTOM, ANCHOR_END, -8 * EDSCALE); + add_child(vbc); + + GridContainer *gc = memnew(GridContainer); + gc->set_columns(2); + vbc->add_child(gc); + + Label *find_label = memnew(Label); + find_label->set_text(TTR("Find: ")); + gc->add_child(find_label); + + _search_text_line_edit = memnew(LineEdit); + _search_text_line_edit->set_h_size_flags(SIZE_EXPAND_FILL); + _search_text_line_edit->connect("text_changed", this, "_on_search_text_modified"); + _search_text_line_edit->connect("text_entered", this, "_on_search_text_entered"); + gc->add_child(_search_text_line_edit); + + { + Control *placeholder = memnew(Control); + gc->add_child(placeholder); + } + + { + HBoxContainer *hbc = memnew(HBoxContainer); + + _whole_words_checkbox = memnew(CheckBox); + _whole_words_checkbox->set_text(TTR("Whole words")); + _whole_words_checkbox->set_pressed(true); + hbc->add_child(_whole_words_checkbox); + + _match_case_checkbox = memnew(CheckBox); + _match_case_checkbox->set_text(TTR("Match case")); + _match_case_checkbox->set_pressed(true); + hbc->add_child(_match_case_checkbox); + + gc->add_child(hbc); + } + + Label *folder_label = memnew(Label); + folder_label->set_text(TTR("Folder: ")); + gc->add_child(folder_label); + + { + HBoxContainer *hbc = memnew(HBoxContainer); + + Label *prefix_label = memnew(Label); + prefix_label->set_text(ROOT_PREFIX); + hbc->add_child(prefix_label); + + _folder_line_edit = memnew(LineEdit); + _folder_line_edit->set_h_size_flags(SIZE_EXPAND_FILL); + hbc->add_child(_folder_line_edit); + + Button *folder_button = memnew(Button); + folder_button->set_text("..."); + folder_button->connect("pressed", this, "_on_folder_button_pressed"); + hbc->add_child(folder_button); + + _folder_dialog = memnew(FileDialog); + _folder_dialog->set_mode(FileDialog::MODE_OPEN_DIR); + _folder_dialog->connect("dir_selected", this, "_on_folder_selected"); + add_child(_folder_dialog); + + gc->add_child(hbc); + } + + Label *filter_label = memnew(Label); + filter_label->set_text(TTR("Filter: ")); + gc->add_child(filter_label); + + { + HBoxContainer *hbc = memnew(HBoxContainer); + + Vector<String> exts; + exts.push_back("gd"); + exts.push_back("cs"); + + for (int i = 0; i < exts.size(); ++i) { + CheckBox *cb = memnew(CheckBox); + cb->set_text(exts[i]); + cb->set_pressed(true); + hbc->add_child(cb); + _filters.push_back(cb); + } + + gc->add_child(hbc); + } + + { + Control *placeholder = memnew(Control); + placeholder->set_custom_minimum_size(Size2(0, EDSCALE * 16)); + vbc->add_child(placeholder); + } + + { + HBoxContainer *hbc = memnew(HBoxContainer); + hbc->set_alignment(HBoxContainer::ALIGN_CENTER); + + _find_button = memnew(Button); + _find_button->set_text(TTR("Find...")); + _find_button->connect("pressed", this, "_on_find_button_pressed"); + _find_button->set_disabled(true); + hbc->add_child(_find_button); + + { + Control *placeholder = memnew(Control); + placeholder->set_custom_minimum_size(Size2(EDSCALE * 16, 0)); + hbc->add_child(placeholder); + } + + _replace_button = memnew(Button); + _replace_button->set_text(TTR("Replace...")); + _replace_button->connect("pressed", this, "_on_replace_button_pressed"); + _replace_button->set_disabled(true); + hbc->add_child(_replace_button); + + { + Control *placeholder = memnew(Control); + placeholder->set_custom_minimum_size(Size2(EDSCALE * 16, 0)); + hbc->add_child(placeholder); + } + + Button *cancel_button = memnew(Button); + cancel_button->set_text(TTR("Cancel")); + cancel_button->connect("pressed", this, "hide"); + hbc->add_child(cancel_button); + + vbc->add_child(hbc); + } +} + +void FindInFilesDialog::set_search_text(String text) { + _search_text_line_edit->set_text(text); +} + +String FindInFilesDialog::get_search_text() const { + String text = _search_text_line_edit->get_text(); + return text.strip_edges(); +} + +bool FindInFilesDialog::is_match_case() const { + return _match_case_checkbox->is_pressed(); +} + +bool FindInFilesDialog::is_whole_words() const { + return _whole_words_checkbox->is_pressed(); +} + +String FindInFilesDialog::get_folder() const { + String text = _folder_line_edit->get_text(); + return text.strip_edges(); +} + +Set<String> FindInFilesDialog::get_filter() const { + Set<String> filters; + for (int i = 0; i < _filters.size(); ++i) { + CheckBox *cb = _filters[i]; + if (cb->is_pressed()) { + filters.insert(_filters[i]->get_text()); + } + } + return filters; +} + +void FindInFilesDialog::_notification(int p_what) { + if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { + if (is_visible()) { + // Doesn't work more than once if not deferred... + _search_text_line_edit->call_deferred("grab_focus"); + _search_text_line_edit->select_all(); + } + } +} + +void FindInFilesDialog::_on_folder_button_pressed() { + _folder_dialog->popup_centered_ratio(); +} + +void FindInFilesDialog::_on_find_button_pressed() { + emit_signal(SIGNAL_FIND_REQUESTED); + hide(); +} + +void FindInFilesDialog::_on_replace_button_pressed() { + emit_signal(SIGNAL_REPLACE_REQUESTED); + hide(); +} + +void FindInFilesDialog::_on_search_text_modified(String text) { + + ERR_FAIL_COND(!_find_button); + ERR_FAIL_COND(!_replace_button); + + _find_button->set_disabled(get_search_text().empty()); + _replace_button->set_disabled(get_search_text().empty()); +} + +void FindInFilesDialog::_on_search_text_entered(String text) { + // This allows to trigger a global search without leaving the keyboard + if (!_find_button->is_disabled()) + _on_find_button_pressed(); +} + +void FindInFilesDialog::_on_folder_selected(String path) { + int i = path.find("://"); + if (i != -1) + path = path.right(i + 3); + _folder_line_edit->set_text(path); +} + +void FindInFilesDialog::_bind_methods() { + + ClassDB::bind_method("_on_folder_button_pressed", &FindInFilesDialog::_on_folder_button_pressed); + ClassDB::bind_method("_on_find_button_pressed", &FindInFilesDialog::_on_find_button_pressed); + ClassDB::bind_method("_on_replace_button_pressed", &FindInFilesDialog::_on_replace_button_pressed); + ClassDB::bind_method("_on_folder_selected", &FindInFilesDialog::_on_folder_selected); + ClassDB::bind_method("_on_search_text_modified", &FindInFilesDialog::_on_search_text_modified); + ClassDB::bind_method("_on_search_text_entered", &FindInFilesDialog::_on_search_text_entered); + + ADD_SIGNAL(MethodInfo(SIGNAL_FIND_REQUESTED)); + ADD_SIGNAL(MethodInfo(SIGNAL_REPLACE_REQUESTED)); +} + +//----------------------------------------------------------------------------- +const char *FindInFilesPanel::SIGNAL_RESULT_SELECTED = "result_selected"; +const char *FindInFilesPanel::SIGNAL_FILES_MODIFIED = "files_modified"; + +FindInFilesPanel::FindInFilesPanel() { + + _finder = memnew(FindInFiles); + _finder->connect(FindInFiles::SIGNAL_RESULT_FOUND, this, "_on_result_found"); + _finder->connect(FindInFiles::SIGNAL_FINISHED, this, "_on_finished"); + add_child(_finder); + + VBoxContainer *vbc = memnew(VBoxContainer); + vbc->set_anchor_and_margin(MARGIN_LEFT, ANCHOR_BEGIN, 0); + vbc->set_anchor_and_margin(MARGIN_TOP, ANCHOR_BEGIN, 0); + vbc->set_anchor_and_margin(MARGIN_RIGHT, ANCHOR_END, 0); + vbc->set_anchor_and_margin(MARGIN_BOTTOM, ANCHOR_END, 0); + add_child(vbc); + + { + HBoxContainer *hbc = memnew(HBoxContainer); + + Label *find_label = memnew(Label); + find_label->set_text(TTR("Find: ")); + hbc->add_child(find_label); + + _search_text_label = memnew(Label); + _search_text_label->add_font_override("font", get_font("source", "EditorFonts")); + hbc->add_child(_search_text_label); + + _progress_bar = memnew(ProgressBar); + _progress_bar->set_h_size_flags(SIZE_EXPAND_FILL); + hbc->add_child(_progress_bar); + set_progress_visible(false); + + _status_label = memnew(Label); + hbc->add_child(_status_label); + + _cancel_button = memnew(Button); + _cancel_button->set_text(TTR("Cancel")); + _cancel_button->connect("pressed", this, "_on_cancel_button_clicked"); + _cancel_button->set_disabled(true); + hbc->add_child(_cancel_button); + + vbc->add_child(hbc); + } + + // In the future, this should be replaced by a more specific list container, + // which can highlight text regions and change opacity for enabled/disabled states + _results_display = memnew(ItemList); + _results_display->add_font_override("font", get_font("source", "EditorFonts")); + _results_display->set_v_size_flags(SIZE_EXPAND_FILL); + _results_display->connect("item_selected", this, "_on_result_selected"); + vbc->add_child(_results_display); + + { + _replace_container = memnew(HBoxContainer); + + Label *replace_label = memnew(Label); + replace_label->set_text(TTR("Replace: ")); + _replace_container->add_child(replace_label); + + _replace_line_edit = memnew(LineEdit); + _replace_line_edit->set_h_size_flags(SIZE_EXPAND_FILL); + _replace_line_edit->connect("text_changed", this, "_on_replace_text_changed"); + _replace_container->add_child(_replace_line_edit); + + _replace_all_button = memnew(Button); + _replace_all_button->set_text(TTR("Replace all (no undo)")); + _replace_all_button->connect("pressed", this, "_on_replace_all_clicked"); + _replace_container->add_child(_replace_all_button); + + _replace_container->hide(); + + vbc->add_child(_replace_container); + } +} + +void FindInFilesPanel::set_with_replace(bool with_replace) { + + _replace_container->set_visible(with_replace); +} + +void FindInFilesPanel::start_search() { + + _results_display->clear(); + _status_label->set_text(TTR("Searching...")); + _search_text_label->set_text(_finder->get_search_text()); + + set_process(true); + set_progress_visible(true); + + _finder->start(); + + update_replace_buttons(); + _cancel_button->set_disabled(false); +} + +void FindInFilesPanel::stop_search() { + + _finder->stop(); + + _status_label->set_text(""); + update_replace_buttons(); + set_progress_visible(false); + _cancel_button->set_disabled(true); +} + +void FindInFilesPanel::_notification(int p_what) { + if (p_what == NOTIFICATION_PROCESS) { + _progress_bar->set_as_ratio(_finder->get_progress()); + } +} + +void FindInFilesPanel::_on_result_found(String fpath, int line_number, int begin, int end, String text) { + + int i = _results_display->get_item_count(); + _results_display->add_item(fpath + ": " + String::num(line_number) + ": " + text.replace("\t", " ")); + _results_display->set_item_metadata(i, varray(fpath, line_number, begin, end)); +} + +void FindInFilesPanel::_on_finished() { + + _status_label->set_text(TTR("Search complete")); + update_replace_buttons(); + set_progress_visible(false); + _cancel_button->set_disabled(true); +} + +void FindInFilesPanel::_on_cancel_button_clicked() { + stop_search(); +} + +void FindInFilesPanel::_on_result_selected(int i) { + + Array meta = _results_display->get_item_metadata(i); + emit_signal(SIGNAL_RESULT_SELECTED, meta[0], meta[1], meta[2], meta[3]); +} + +void FindInFilesPanel::_on_replace_text_changed(String text) { + update_replace_buttons(); +} + +void FindInFilesPanel::_on_replace_all_clicked() { + + String replace_text = get_replace_text(); + ERR_FAIL_COND(replace_text.empty()); + + String last_fpath; + PoolIntArray locations; + PoolStringArray modified_files; + + for (int i = 0; i < _results_display->get_item_count(); ++i) { + + Array meta = _results_display->get_item_metadata(i); + + String fpath = meta[0]; + + // Results are sorted by file, so we can batch replaces + if (fpath != last_fpath) { + if (locations.size() != 0) { + apply_replaces_in_file(last_fpath, locations, replace_text); + modified_files.append(last_fpath); + locations.resize(0); + } + } + + locations.append(meta[1]); // line_number + locations.append(meta[2]); // begin + locations.append(meta[3]); // end + + last_fpath = fpath; + } + + if (locations.size() != 0) { + apply_replaces_in_file(last_fpath, locations, replace_text); + modified_files.append(last_fpath); + } + + // Hide replace bar so we can't trigger the action twice without doing a new search + set_with_replace(false); + + emit_signal(SIGNAL_FILES_MODIFIED, modified_files); +} + +// Same as get_line, but preserves line ending characters +class ConservativeGetLine { +public: + String get_line(FileAccess *f) { + + _line_buffer.clear(); + + CharType c = f->get_8(); + + while (!f->eof_reached()) { + + if (c == '\n') { + _line_buffer.push_back(c); + _line_buffer.push_back(0); + return String::utf8(_line_buffer.ptr()); + + } else if (c == '\0') { + _line_buffer.push_back(c); + return String::utf8(_line_buffer.ptr()); + + } else if (c != '\r') { + _line_buffer.push_back(c); + } + + c = f->get_8(); + } + + _line_buffer.push_back(0); + return String::utf8(_line_buffer.ptr()); + } + +private: + Vector<char> _line_buffer; +}; + +void FindInFilesPanel::apply_replaces_in_file(String fpath, PoolIntArray locations, String text) { + + ERR_FAIL_COND(locations.size() % 3 != 0); + + //print_line(String("Replacing {0} occurrences in {1}").format(varray(fpath, locations.size() / 3))); + + // If the file is already open, I assume the editor will reload it. + // If there are unsaved changes, the user will be asked on focus, + // however that means either loosing changes or loosing replaces. + + FileAccess *f = FileAccess::open(fpath, FileAccess::READ); + ERR_FAIL_COND(f == NULL); + + String buffer; + int current_line = 1; + + ConservativeGetLine conservative; + + String line = conservative.get_line(f); + + PoolIntArray::Read locations_read = locations.read(); + for (int i = 0; i < locations.size(); i += 3) { + + int repl_line_number = locations_read[i]; + int repl_begin = locations_read[i + 1]; + int repl_end = locations_read[i + 2]; + + while (current_line < repl_line_number) { + buffer += line; + line = conservative.get_line(f); + ++current_line; + } + + line = line.left(repl_begin) + text + line.right(repl_end); + } + + buffer += line; + + while (!f->eof_reached()) { + buffer += conservative.get_line(f); + } + + // Now the modified contents are in the buffer, rewrite the file with our changes + + Error err = f->reopen(fpath, FileAccess::WRITE); + ERR_FAIL_COND(err != OK); + + f->store_string(buffer); + + f->close(); +} + +String FindInFilesPanel::get_replace_text() { + return _replace_line_edit->get_text().strip_edges(); +} + +void FindInFilesPanel::update_replace_buttons() { + + String text = get_replace_text(); + bool disabled = text.empty() || _finder->is_searching(); + + _replace_all_button->set_disabled(disabled); +} + +void FindInFilesPanel::set_progress_visible(bool visible) { + _progress_bar->set_self_modulate(Color(1, 1, 1, visible ? 1 : 0)); +} + +void FindInFilesPanel::_bind_methods() { + + ClassDB::bind_method("_on_result_found", &FindInFilesPanel::_on_result_found); + ClassDB::bind_method("_on_finished", &FindInFilesPanel::_on_finished); + ClassDB::bind_method("_on_cancel_button_clicked", &FindInFilesPanel::_on_cancel_button_clicked); + ClassDB::bind_method("_on_result_selected", &FindInFilesPanel::_on_result_selected); + ClassDB::bind_method("_on_replace_text_changed", &FindInFilesPanel::_on_replace_text_changed); + ClassDB::bind_method("_on_replace_all_clicked", &FindInFilesPanel::_on_replace_all_clicked); + + ADD_SIGNAL(MethodInfo(SIGNAL_RESULT_SELECTED, + PropertyInfo(Variant::STRING, "path"), + PropertyInfo(Variant::INT, "line_number"), + PropertyInfo(Variant::INT, "begin"), + PropertyInfo(Variant::INT, "end"))); + + ADD_SIGNAL(MethodInfo(SIGNAL_FILES_MODIFIED, PropertyInfo(Variant::STRING, "paths"))); +} diff --git a/editor/find_in_files.h b/editor/find_in_files.h new file mode 100644 index 0000000000..d57184960b --- /dev/null +++ b/editor/find_in_files.h @@ -0,0 +1,184 @@ +/*************************************************************************/ +/* find_in_files.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 FIND_IN_FILES_H +#define FIND_IN_FILES_H + +#include "scene/gui/dialogs.h" + +// Performs the actual search +class FindInFiles : public Node { + GDCLASS(FindInFiles, Node) +public: + static const char *SIGNAL_RESULT_FOUND; + static const char *SIGNAL_FINISHED; + + FindInFiles(); + + void set_search_text(String p_pattern); + void set_whole_words(bool p_whole_word); + void set_match_case(bool p_match_case); + void set_folder(String folder); + void set_filter(const Set<String> &exts); + + String get_search_text() const { return _pattern; } + + bool is_whole_words() const { return _whole_words; } + bool is_match_case() const { return _match_case; } + + void start(); + void stop(); + + bool is_searching() const { return _searching; } + float get_progress() const; + +protected: + void _notification(int p_notification); + + static void _bind_methods(); + +private: + void _process(); + void _iterate(); + void _scan_dir(String path, PoolStringArray &out_folders); + void _scan_file(String fpath); + + // Config + String _pattern; + Set<String> _extension_filter; + String _root_prefix; + String _root_dir; + bool _whole_words; + bool _match_case; + + // State + bool _searching; + String _current_dir; + Vector<PoolStringArray> _folders_stack; + Vector<String> _files_to_scan; + int _initial_files_count; +}; + +class LineEdit; +class CheckBox; +class FileDialog; + +// Prompts search parameters +class FindInFilesDialog : public WindowDialog { + GDCLASS(FindInFilesDialog, WindowDialog) +public: + static const char *SIGNAL_FIND_REQUESTED; + static const char *SIGNAL_REPLACE_REQUESTED; + + FindInFilesDialog(); + + void set_search_text(String text); + + String get_search_text() const; + bool is_match_case() const; + bool is_whole_words() const; + String get_folder() const; + Set<String> get_filter() const; + +protected: + static void _bind_methods(); + + void _notification(int p_what); + +private: + void _on_folder_button_pressed(); + void _on_find_button_pressed(); + void _on_replace_button_pressed(); + void _on_folder_selected(String path); + void _on_search_text_modified(String text); + void _on_search_text_entered(String text); + + LineEdit *_search_text_line_edit; + LineEdit *_folder_line_edit; + Vector<CheckBox *> _filters; + CheckBox *_match_case_checkbox; + CheckBox *_whole_words_checkbox; + Button *_find_button; + Button *_replace_button; + FileDialog *_folder_dialog; +}; + +class Button; +class ItemList; +class ProgressBar; + +// Display search results +class FindInFilesPanel : public Control { + GDCLASS(FindInFilesPanel, Control) +public: + static const char *SIGNAL_RESULT_SELECTED; + static const char *SIGNAL_FILES_MODIFIED; + + FindInFilesPanel(); + + FindInFiles *get_finder() const { return _finder; } + + void set_with_replace(bool with_replace); + + void start_search(); + void stop_search(); + +protected: + static void _bind_methods(); + + void _notification(int p_what); + +private: + void _on_result_found(String fpath, int line_number, int begin, int end, String text); + void _on_finished(); + void _on_cancel_button_clicked(); + void _on_result_selected(int i); + void _on_replace_text_changed(String text); + void _on_replace_all_clicked(); + + void apply_replaces_in_file(String fpath, PoolIntArray locations, String text); + + void update_replace_buttons(); + String get_replace_text(); + void set_progress_visible(bool visible); + + FindInFiles *_finder; + Label *_search_text_label; + ItemList *_results_display; + Label *_status_label; + Button *_cancel_button; + ProgressBar *_progress_bar; + + HBoxContainer *_replace_container; + LineEdit *_replace_line_edit; + Button *_replace_all_button; +}; + +#endif // FIND_IN_FILES_H diff --git a/editor/icons/icon_editor_position.svg b/editor/icons/icon_editor_position.svg new file mode 100644 index 0000000000..7cbce07fab --- /dev/null +++ b/editor/icons/icon_editor_position.svg @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg919" + sodipodi:docname="icon_editor_position.svg" + inkscape:version="0.92.2 5c3e80d, 2017-08-06"> + <metadata + id="metadata925"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs923" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="2550" + inkscape:window-height="1414" + id="namedview921" + showgrid="false" + inkscape:zoom="64" + inkscape:cx="13.492036" + inkscape:cy="5.8518769" + inkscape:window-x="1370" + inkscape:window-y="20" + inkscape:window-maximized="0" + inkscape:current-layer="svg919" + inkscape:snap-page="true" /> + <g + id="g6479"> + <path + id="path913" + d="M 6 0 L 6 4.4199219 A 4.2661548 4.0576186 0 0 0 4.2910156 6 L 0 6 L 0 10 L 4.2949219 10 A 4.2661548 4.0576186 0 0 0 6 11.582031 L 6 16 L 10 16 L 10 11.580078 A 4.2661548 4.0576186 0 0 0 11.708984 10 L 16 10 L 16 6 L 11.705078 6 A 4.2661548 4.0576186 0 0 0 10 4.4179688 L 10 0 L 6 0 z " + style="fill:#ffffff;fill-opacity:0.70588237" /> + <path + id="path915" + d="M 7 1 L 7 4.0605469 A 4.2661548 4.0576186 0 0 1 8 3.9414062 A 4.2661548 4.0576186 0 0 1 9 4.0605469 L 9 1 L 7 1 z M 1 7 L 1 9 L 3.8691406 9 A 4.2661548 4.0576186 0 0 1 3.734375 8 A 4.2661548 4.0576186 0 0 1 3.8710938 7 L 1 7 z M 12.130859 7 A 4.2661548 4.0576186 0 0 1 12.265625 8 A 4.2661548 4.0576186 0 0 1 12.128906 9 L 15 9 L 15 7 L 12.130859 7 z M 7 11.939453 L 7 15 L 9 15 L 9 11.939453 A 4.2661548 4.0576186 0 0 1 8 12.058594 A 4.2661548 4.0576186 0 0 1 7 11.939453 z " + style="fill:#ff8484;stroke:none;fill-opacity:1" /> + <circle + id="circle1517" + r="2.9201488" + cy="8" + cx="8" + style="fill:#ff8484;fill-opacity:1;stroke-width:0.97338283" /> + </g> +</svg> diff --git a/editor/icons/icon_editor_position_previous.svg b/editor/icons/icon_editor_position_previous.svg new file mode 100644 index 0000000000..d9eb7d7ce2 --- /dev/null +++ b/editor/icons/icon_editor_position_previous.svg @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg919" + sodipodi:docname="icon_editor_position_previous.svg" + inkscape:version="0.92.2 5c3e80d, 2017-08-06"> + <metadata + id="metadata925"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs923" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1356" + inkscape:window-height="742" + id="namedview921" + showgrid="false" + inkscape:zoom="11.313709" + inkscape:cx="14.523034" + inkscape:cy="-5.7323703" + inkscape:window-x="4" + inkscape:window-y="20" + inkscape:window-maximized="0" + inkscape:current-layer="svg919" + inkscape:snap-page="true" /> + <path + style="fill:#6699ff;stroke:none;fill-opacity:0.69803923" + d="M 7 1 L 7 4.0605469 A 4.2661548 4.0576186 0 0 1 8 3.9414062 A 4.2661548 4.0576186 0 0 1 9 4.0605469 L 9 1 L 7 1 z M 1 7 L 1 9 L 3.8691406 9 A 4.2661548 4.0576186 0 0 1 3.734375 8 A 4.2661548 4.0576186 0 0 1 3.8710938 7 L 1 7 z M 12.130859 7 A 4.2661548 4.0576186 0 0 1 12.265625 8 A 4.2661548 4.0576186 0 0 1 12.128906 9 L 15 9 L 15 7 L 12.130859 7 z M 7 11.939453 L 7 15 L 9 15 L 9 11.939453 A 4.2661548 4.0576186 0 0 1 8 12.058594 A 4.2661548 4.0576186 0 0 1 7 11.939453 z " + id="path915" /> + <circle + style="fill:#6699ff;fill-opacity:0.69803923;stroke-width:0.97338283" + cx="8" + cy="8" + r="2.9201488" + id="circle1517" /> +</svg> diff --git a/editor/icons/icon_editor_position_unselected.svg b/editor/icons/icon_editor_position_unselected.svg new file mode 100644 index 0000000000..8f32c89f16 --- /dev/null +++ b/editor/icons/icon_editor_position_unselected.svg @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg919" + sodipodi:docname="icon_editor_position_unselected.svg" + inkscape:version="0.92.2 5c3e80d, 2017-08-06"> + <metadata + id="metadata925"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs923" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="2550" + inkscape:window-height="1414" + id="namedview921" + showgrid="false" + inkscape:zoom="64" + inkscape:cx="13.492036" + inkscape:cy="8.3518769" + inkscape:window-x="1370" + inkscape:window-y="20" + inkscape:window-maximized="0" + inkscape:current-layer="svg919" + inkscape:snap-page="true" /> + <path + style="fill:#000000;fill-opacity:0.41077441" + d="M 6 0 L 6 4.4199219 A 4.2661548 4.0576186 0 0 0 4.2910156 6 L 0 6 L 0 10 L 4.2949219 10 A 4.2661548 4.0576186 0 0 0 6 11.582031 L 6 16 L 10 16 L 10 11.580078 A 4.2661548 4.0576186 0 0 0 11.708984 10 L 16 10 L 16 6 L 11.705078 6 A 4.2661548 4.0576186 0 0 0 10 4.4179688 L 10 0 L 6 0 z " + id="path913" /> + <path + style="fill:#d9d9d9;stroke:none;fill-opacity:1" + d="M 7 1 L 7 4.0605469 A 4.2661548 4.0576186 0 0 1 8 3.9414062 A 4.2661548 4.0576186 0 0 1 9 4.0605469 L 9 1 L 7 1 z M 1 7 L 1 9 L 3.8691406 9 A 4.2661548 4.0576186 0 0 1 3.734375 8 A 4.2661548 4.0576186 0 0 1 3.8710938 7 L 1 7 z M 12.130859 7 A 4.2661548 4.0576186 0 0 1 12.265625 8 A 4.2661548 4.0576186 0 0 1 12.128906 9 L 15 9 L 15 7 L 12.130859 7 z M 7 11.939453 L 7 15 L 9 15 L 9 11.939453 A 4.2661548 4.0576186 0 0 1 8 12.058594 A 4.2661548 4.0576186 0 0 1 7 11.939453 z " + id="path915" /> + <circle + style="fill:#d9d9d9;fill-opacity:1;stroke-width:0.97338283" + cx="8" + cy="8" + r="2.9201488" + id="circle1517" /> +</svg> diff --git a/editor/import/editor_import_collada.cpp b/editor/import/editor_import_collada.cpp index 863b13cbd7..c1e897a04c 100644 --- a/editor/import/editor_import_collada.cpp +++ b/editor/import/editor_import_collada.cpp @@ -480,7 +480,7 @@ Error ColladaImport::_create_material(const String &p_target) { } } - float roughness = Math::sqrt(1.0 - ((Math::log(effect.shininess) / Math::log(2.0)) / 8.0)); //not very right.. + float roughness = (effect.shininess - 1.0) / 510; material->set_roughness(roughness); if (effect.double_sided) { diff --git a/editor/import/editor_scene_importer_gltf.cpp b/editor/import/editor_scene_importer_gltf.cpp index 321d29f2f6..af79f9946a 100644 --- a/editor/import/editor_scene_importer_gltf.cpp +++ b/editor/import/editor_scene_importer_gltf.cpp @@ -1732,14 +1732,19 @@ void EditorSceneImporterGLTF::_generate_bone(GLTFState &state, int p_node, Vecto for (int i = 0; i < n->joints.size(); i++) { ERR_FAIL_COND(n->joints[i].skin < 0); - int bone_index = skeletons[n->joints[i].skin]->get_bone_count(); - skeletons[n->joints[i].skin]->add_bone(n->name); + int bone_index = n->joints[i].bone; + + Skeleton *s = skeletons[n->joints[i].skin]; + while (s->get_bone_count() <= bone_index) { + s->add_bone("Bone " + itos(s->get_bone_count())); + } + if (p_parent_bones.size()) { - skeletons[n->joints[i].skin]->set_bone_parent(bone_index, p_parent_bones[i]); + s->set_bone_parent(bone_index, p_parent_bones[i]); } - skeletons[n->joints[i].skin]->set_bone_rest(bone_index, state.skins[n->joints[i].skin].bones[n->joints[i].bone].inverse_bind.affine_inverse()); + s->set_bone_rest(bone_index, state.skins[n->joints[i].skin].bones[n->joints[i].bone].inverse_bind.affine_inverse()); - n->godot_nodes.push_back(skeletons[n->joints[i].skin]); + n->godot_nodes.push_back(s); n->joints[i].godot_bone_index = bone_index; parent_bones.push_back(bone_index); } diff --git a/editor/import/resource_importer_wav.cpp b/editor/import/resource_importer_wav.cpp index 03155b3a48..debdeb1c4a 100644 --- a/editor/import/resource_importer_wav.cpp +++ b/editor/import/resource_importer_wav.cpp @@ -304,17 +304,23 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s int limit_rate_hz = p_options["force/max_rate_hz"]; if (limit_rate && rate > limit_rate_hz && rate > 0 && frames > 0) { //resampleeee!!! - int new_data_frames = frames * limit_rate_hz / rate; + int new_data_frames = (int)(frames * (float)limit_rate_hz / (float)rate); + + print_line("\tresampling ratio: " + rtos((float)limit_rate_hz / (float)rate)); + print_line("\tnew frames: " + itos(new_data_frames)); + Vector<float> new_data; new_data.resize(new_data_frames * format_channels); for (int c = 0; c < format_channels; c++) { + float frac = .0f; + int ipos = 0; + for (int i = 0; i < new_data_frames; i++) { //simple cubic interpolation should be enough. - float pos = float(i) * frames / new_data_frames; - float mu = pos - Math::floor(pos); - int ipos = int(Math::floor(pos)); + + float mu = frac; float y0 = data[MAX(0, ipos - 1) * format_channels + c]; float y1 = data[ipos * format_channels + c]; @@ -330,14 +336,22 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s float res = (a0 * mu * mu2 + a1 * mu2 + a2 * mu + a3); new_data[i * format_channels + c] = res; + + // update position and always keep fractional part within ]0...1] + // in order to avoid 32bit floating point precision errors + + frac += (float)rate / (float)limit_rate_hz; + int tpos = (int)Math::floor(frac); + ipos += tpos; + frac -= tpos; } } if (loop) { - - loop_begin = loop_begin * new_data_frames / frames; - loop_end = loop_end * new_data_frames / frames; + loop_begin = (int)(loop_begin * (float)new_data_frames / (float)frames); + loop_end = (int)(loop_end * (float)new_data_frames / (float)frames); } + data = new_data; rate = limit_rate_hz; frames = new_data_frames; diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 04c9246aed..7612f18aff 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -1679,10 +1679,10 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay onion_skinning->get_popup()->add_separator(); onion_skinning->get_popup()->add_item(TTR("Depth"), -1); onion_skinning->get_popup()->set_item_disabled(onion_skinning->get_popup()->get_item_count() - 1, true); - onion_skinning->get_popup()->add_check_item(TTR("1 step"), ONION_SKINNING_1_STEP); + onion_skinning->get_popup()->add_radio_check_item(TTR("1 step"), ONION_SKINNING_1_STEP); onion_skinning->get_popup()->set_item_checked(onion_skinning->get_popup()->get_item_count() - 1, true); - onion_skinning->get_popup()->add_check_item(TTR("2 steps"), ONION_SKINNING_2_STEPS); - onion_skinning->get_popup()->add_check_item(TTR("3 steps"), ONION_SKINNING_3_STEPS); + onion_skinning->get_popup()->add_radio_check_item(TTR("2 steps"), ONION_SKINNING_2_STEPS); + onion_skinning->get_popup()->add_radio_check_item(TTR("3 steps"), ONION_SKINNING_3_STEPS); onion_skinning->get_popup()->add_separator(); onion_skinning->get_popup()->add_check_item(TTR("Differences Only"), ONION_SKINNING_DIFFERENCES_ONLY); onion_skinning->get_popup()->add_check_item(TTR("Force White Modulate"), ONION_SKINNING_FORCE_WHITE_MODULATE); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 0184bff050..bb94aee462 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -196,11 +196,15 @@ void CanvasItemEditor::_snap_other_nodes(Point2 p_value, Point2 &r_current_snap, Transform2D ci_transform = canvas_item->get_global_transform_with_canvas(); Transform2D to_snap_transform = p_to_snap ? p_to_snap->get_global_transform_with_canvas() : Transform2D(); if (fmod(ci_transform.get_rotation() - to_snap_transform.get_rotation(), (real_t)360.0) == 0.0) { - Point2 begin = ci_transform.xform(canvas_item->_edit_get_rect().get_position()); - Point2 end = ci_transform.xform(canvas_item->_edit_get_rect().get_position() + canvas_item->_edit_get_rect().get_size()); - - _snap_if_closer_point(p_value, begin, r_current_snap, r_snapped, ci_transform.get_rotation()); - _snap_if_closer_point(p_value, end, r_current_snap, r_snapped, ci_transform.get_rotation()); + if (canvas_item->_edit_use_rect()) { + Point2 begin = ci_transform.xform(canvas_item->_edit_get_rect().get_position()); + Point2 end = ci_transform.xform(canvas_item->_edit_get_rect().get_position() + canvas_item->_edit_get_rect().get_size()); + _snap_if_closer_point(p_value, begin, r_current_snap, r_snapped, ci_transform.get_rotation()); + _snap_if_closer_point(p_value, end, r_current_snap, r_snapped, ci_transform.get_rotation()); + } else { + Point2 position = ci_transform.xform(Point2()); + _snap_if_closer_point(p_value, position, r_current_snap, r_snapped, ci_transform.get_rotation()); + } } } for (int i = 0; i < p_current->get_child_count(); i++) { @@ -221,23 +225,23 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, const // Parent sides and center if ((is_snap_active && snap_node_parent && (p_modes & SNAP_NODE_PARENT)) || (p_forced_modes & SNAP_NODE_PARENT)) { - Point2 begin; - Point2 end; - bool can_snap = false; if (const Control *c = Object::cast_to<Control>(p_canvas_item)) { - begin = p_canvas_item->get_global_transform_with_canvas().xform(_anchor_to_position(c, Point2(0, 0))); - end = p_canvas_item->get_global_transform_with_canvas().xform(_anchor_to_position(c, Point2(1, 1))); - can_snap = true; - } else if (const CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_canvas_item->get_parent())) { - begin = p_canvas_item->get_transform().affine_inverse().xform(parent_ci->_edit_get_rect().get_position()); - end = p_canvas_item->get_transform().affine_inverse().xform(parent_ci->_edit_get_rect().get_position() + parent_ci->_edit_get_rect().get_size()); - can_snap = true; - } - - if (can_snap) { + Point2 begin = p_canvas_item->get_global_transform_with_canvas().xform(_anchor_to_position(c, Point2(0, 0))); + Point2 end = p_canvas_item->get_global_transform_with_canvas().xform(_anchor_to_position(c, Point2(1, 1))); _snap_if_closer_point(p_target, begin, output, snapped, rotation); _snap_if_closer_point(p_target, (begin + end) / 2.0, output, snapped, rotation); _snap_if_closer_point(p_target, end, output, snapped, rotation); + } else if (const CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_canvas_item->get_parent())) { + if (parent_ci->_edit_use_rect()) { + Point2 begin = p_canvas_item->get_transform().affine_inverse().xform(parent_ci->_edit_get_rect().get_position()); + Point2 end = p_canvas_item->get_transform().affine_inverse().xform(parent_ci->_edit_get_rect().get_position() + parent_ci->_edit_get_rect().get_size()); + _snap_if_closer_point(p_target, begin, output, snapped, rotation); + _snap_if_closer_point(p_target, (begin + end) / 2.0, output, snapped, rotation); + _snap_if_closer_point(p_target, end, output, snapped, rotation); + } else { + Point2 position = p_canvas_item->get_transform().affine_inverse().xform(Point2()); + _snap_if_closer_point(p_target, position, output, snapped, rotation); + } } } @@ -253,16 +257,23 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, const // Self sides if ((is_snap_active && snap_node_sides && (p_modes & SNAP_NODE_SIDES)) || (p_forced_modes & SNAP_NODE_SIDES)) { - Point2 begin = p_canvas_item->get_global_transform_with_canvas().xform(p_canvas_item->_edit_get_rect().get_position()); - Point2 end = p_canvas_item->get_global_transform_with_canvas().xform(p_canvas_item->_edit_get_rect().get_position() + p_canvas_item->_edit_get_rect().get_size()); - _snap_if_closer_point(p_target, begin, output, snapped, rotation); - _snap_if_closer_point(p_target, end, output, snapped, rotation); + if (p_canvas_item->_edit_use_rect()) { + Point2 begin = p_canvas_item->get_global_transform_with_canvas().xform(p_canvas_item->_edit_get_rect().get_position()); + Point2 end = p_canvas_item->get_global_transform_with_canvas().xform(p_canvas_item->_edit_get_rect().get_position() + p_canvas_item->_edit_get_rect().get_size()); + _snap_if_closer_point(p_target, begin, output, snapped, rotation); + _snap_if_closer_point(p_target, end, output, snapped, rotation); + } } // Self center if ((is_snap_active && snap_node_center && (p_modes & SNAP_NODE_CENTER)) || (p_forced_modes & SNAP_NODE_CENTER)) { - Point2 center = p_canvas_item->get_global_transform_with_canvas().xform(p_canvas_item->_edit_get_rect().get_position() + p_canvas_item->_edit_get_rect().get_size() / 2.0); - _snap_if_closer_point(p_target, center, output, snapped, rotation); + if (p_canvas_item->_edit_use_rect()) { + Point2 center = p_canvas_item->get_global_transform_with_canvas().xform(p_canvas_item->_edit_get_rect().get_position() + p_canvas_item->_edit_get_rect().get_size() / 2.0); + _snap_if_closer_point(p_target, center, output, snapped, rotation); + } else { + Point2 position = p_canvas_item->get_global_transform_with_canvas().xform(Point2()); + _snap_if_closer_point(p_target, position, output, snapped, rotation); + } } } @@ -364,63 +375,85 @@ Rect2 CanvasItemEditor::_get_encompassing_rect_from_list(List<CanvasItem *> p_li // Handles the first element CanvasItem *canvas_item = p_list.front()->get(); - Rect2 rect = Rect2(canvas_item->get_global_transform_with_canvas().xform(canvas_item->_edit_get_rect().position + canvas_item->_edit_get_rect().size / 2), Size2()); + Rect2 rect; + if (canvas_item->_edit_use_rect()) { + rect = Rect2(canvas_item->get_global_transform_with_canvas().xform(canvas_item->_edit_get_rect().position + canvas_item->_edit_get_rect().size / 2), Size2()); + } else { + rect = Rect2(canvas_item->get_global_transform_with_canvas().xform(Point2()), Size2()); + } - // Handles the other ones + // Expand with the other ones for (List<CanvasItem *>::Element *E = p_list.front(); E; E = E->next()) { CanvasItem *canvas_item = E->get(); - Rect2 current_rect = canvas_item->_edit_get_rect(); Transform2D xform = canvas_item->get_global_transform_with_canvas(); - rect.expand_to(xform.xform(current_rect.position)); - rect.expand_to(xform.xform(current_rect.position + Vector2(current_rect.size.x, 0))); - rect.expand_to(xform.xform(current_rect.position + current_rect.size)); - rect.expand_to(xform.xform(current_rect.position + Vector2(0, current_rect.size.y))); + if (canvas_item->_edit_use_rect()) { + Rect2 current_rect = canvas_item->_edit_get_rect(); + + rect.expand_to(xform.xform(current_rect.position)); + rect.expand_to(xform.xform(current_rect.position + Vector2(current_rect.size.x, 0))); + rect.expand_to(xform.xform(current_rect.position + current_rect.size)); + rect.expand_to(xform.xform(current_rect.position + Vector2(0, current_rect.size.y))); + } else { + rect.expand_to(xform.xform(Point2())); + } } return rect; } -void CanvasItemEditor::_expand_encompassing_rect_using_children(Rect2 &r_rect, Node *p_node, bool &r_first, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) { +void CanvasItemEditor::_expand_encompassing_rect_using_children(Rect2 &r_rect, const Node *p_node, bool &r_first, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) { if (!p_node) return; if (Object::cast_to<Viewport>(p_node)) return; - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); + const CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); - bool inherited = p_node != get_tree()->get_edited_scene_root() && p_node->get_filename() != ""; - bool editable = inherited && EditorNode::get_singleton()->get_edited_scene()->is_editable_instance(p_node); + /*bool inherited = p_node != get_tree()->get_edited_scene_root() && p_node->get_filename() != ""; + bool editable = !inherited || EditorNode::get_singleton()->get_edited_scene()->is_editable_instance(p_node); bool lock_children = p_node->has_meta("_edit_group_") && p_node->get_meta("_edit_group_"); - if (!lock_children && (!inherited || editable)) { - for (int i = p_node->get_child_count() - 1; i >= 0; i--) { - if (canvas_item && !canvas_item->is_set_as_toplevel()) { - _expand_encompassing_rect_using_children(r_rect, p_node->get_child(i), r_first, p_parent_xform * canvas_item->get_transform(), p_canvas_xform); - } else { - CanvasLayer *canvas_layer = Object::cast_to<CanvasLayer>(p_node); - _expand_encompassing_rect_using_children(r_rect, p_node->get_child(i), r_first, Transform2D(), canvas_layer ? canvas_layer->get_transform() : p_canvas_xform); - } + if (!lock_children && editable) {}*/ + + for (int i = p_node->get_child_count() - 1; i >= 0; i--) { + if (canvas_item && !canvas_item->is_set_as_toplevel()) { + _expand_encompassing_rect_using_children(r_rect, p_node->get_child(i), r_first, p_parent_xform * canvas_item->get_transform(), p_canvas_xform); + } else { + const CanvasLayer *canvas_layer = Object::cast_to<CanvasLayer>(p_node); + _expand_encompassing_rect_using_children(r_rect, p_node->get_child(i), r_first, Transform2D(), canvas_layer ? canvas_layer->get_transform() : p_canvas_xform); } } if (canvas_item && canvas_item->is_visible_in_tree() && !canvas_item->has_meta("_edit_lock_")) { - Rect2 rect = canvas_item->_edit_get_rect(); Transform2D xform = p_parent_xform * p_canvas_xform * canvas_item->get_transform(); - if (r_first) { - r_rect = Rect2(xform.xform(rect.position + rect.size / 2), Size2()); - r_first = false; + if (canvas_item->_edit_use_rect()) { + Rect2 rect = canvas_item->_edit_get_rect(); + if (r_first) { + r_rect = Rect2(xform.xform(rect.position + rect.size / 2), Size2()); + r_first = false; + } + if (r_rect.size != Size2()) { + r_rect.expand_to(xform.xform(rect.position)); + r_rect.expand_to(xform.xform(rect.position + Point2(rect.size.x, 0))); + r_rect.expand_to(xform.xform(rect.position + Point2(0, rect.size.y))); + r_rect.expand_to(xform.xform(rect.position + rect.size)); + } + } else { + if (r_first) { + r_rect = Rect2(xform.xform(Point2()), Size2()); + r_first = false; + } else { + r_rect.expand_to(xform.xform(Point2())); + } } - r_rect.expand_to(xform.xform(rect.position)); - r_rect.expand_to(xform.xform(rect.position + Point2(rect.size.x, 0))); - r_rect.expand_to(xform.xform(rect.position + Point2(0, rect.size.y))); - r_rect.expand_to(xform.xform(rect.position + rect.size)); } } -Rect2 CanvasItemEditor::_get_scene_encompassing_rect() { +Rect2 CanvasItemEditor::_get_encompassing_rect(const Node *p_node) { Rect2 rect; bool first = true; - _expand_encompassing_rect_using_children(rect, editor->get_edited_scene(), first); + _expand_encompassing_rect_using_children(rect, p_node, first); + return rect; } @@ -433,22 +466,23 @@ void CanvasItemEditor::_find_canvas_items_at_pos(const Point2 &p_pos, Node *p_no const real_t grab_distance = EDITOR_DEF("editors/poly_editor/point_grab_radius", 8); CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); - for (int i = p_node->get_child_count() - 1; i >= 0; i--) { - if (canvas_item && !canvas_item->is_set_as_toplevel()) - _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, 0, p_parent_xform * canvas_item->get_transform(), p_canvas_xform); - else { - CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node); - _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, 0, Transform2D(), cl ? cl->get_transform() : p_canvas_xform); //use base transform + Node *scene = editor->get_edited_scene(); + if (!(p_node != scene && p_node->get_owner() != scene && (!scene->is_editable_instance(p_node) || p_node->has_meta("_edit_lock_")))) { + for (int i = p_node->get_child_count() - 1; i >= 0; i--) { + if (canvas_item && !canvas_item->is_set_as_toplevel()) { + _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, limit, p_parent_xform * canvas_item->get_transform(), p_canvas_xform); + } else { + CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node); + _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, limit, Transform2D(), cl ? cl->get_transform() : p_canvas_xform); + } + if (limit != 0 && r_items.size() >= limit) + return; } - if (limit != 0 && r_items.size() >= limit) - return; } - if (canvas_item && canvas_item->is_visible_in_tree() && !canvas_item->has_meta("_edit_lock_")) { - + if (canvas_item && canvas_item->is_visible_in_tree() && (canvas_item->get_owner() != editor->get_edited_scene() || !canvas_item->has_meta("_edit_lock_"))) { Transform2D xform = (p_parent_xform * p_canvas_xform * canvas_item->get_transform()).affine_inverse(); const real_t local_grab_distance = xform.basis_xform(Vector2(grab_distance, 0)).length(); - if (canvas_item->_edit_is_selected_on_click(xform.xform(p_pos), local_grab_distance)) { Node2D *node = Object::cast_to<Node2D>(canvas_item); @@ -463,7 +497,7 @@ void CanvasItemEditor::_find_canvas_items_at_pos(const Point2 &p_pos, Node *p_no return; } -void CanvasItemEditor::_find_canvas_items_at_rect(const Rect2 &p_rect, Node *p_node, List<CanvasItem *> *r_items, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) { +void CanvasItemEditor::_find_canvas_items_in_rect(const Rect2 &p_rect, Node *p_node, List<CanvasItem *> *r_items, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) { if (!p_node) return; if (Object::cast_to<Viewport>(p_node)) @@ -472,31 +506,37 @@ void CanvasItemEditor::_find_canvas_items_at_rect(const Rect2 &p_rect, Node *p_n CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); bool inherited = p_node != get_tree()->get_edited_scene_root() && p_node->get_filename() != ""; - bool editable = inherited && EditorNode::get_singleton()->get_edited_scene()->is_editable_instance(p_node); + bool editable = !inherited || EditorNode::get_singleton()->get_edited_scene()->is_editable_instance(p_node); bool lock_children = p_node->has_meta("_edit_group_") && p_node->get_meta("_edit_group_"); + bool locked = p_node->has_meta("_edit_lock_") && p_node->get_meta("_edit_lock_"); - if (!lock_children && (!inherited || editable)) { + if (!lock_children && !locked && editable) { for (int i = p_node->get_child_count() - 1; i >= 0; i--) { if (canvas_item && !canvas_item->is_set_as_toplevel()) { - _find_canvas_items_at_rect(p_rect, p_node->get_child(i), r_items, p_parent_xform * canvas_item->get_transform(), p_canvas_xform); + _find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, p_parent_xform * canvas_item->get_transform(), p_canvas_xform); } else { CanvasLayer *canvas_layer = Object::cast_to<CanvasLayer>(p_node); - _find_canvas_items_at_rect(p_rect, p_node->get_child(i), r_items, Transform2D(), canvas_layer ? canvas_layer->get_transform() : p_canvas_xform); + _find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, Transform2D(), canvas_layer ? canvas_layer->get_transform() : p_canvas_xform); } } } if (canvas_item && canvas_item->is_visible_in_tree() && !canvas_item->has_meta("_edit_lock_")) { - - Rect2 rect = canvas_item->_edit_get_rect(); Transform2D xform = p_parent_xform * p_canvas_xform * canvas_item->get_transform(); - if (p_rect.has_point(xform.xform(rect.position)) && - p_rect.has_point(xform.xform(rect.position + Vector2(rect.size.x, 0))) && - p_rect.has_point(xform.xform(rect.position + Vector2(rect.size.x, rect.size.y))) && - p_rect.has_point(xform.xform(rect.position + Vector2(0, rect.size.y)))) { + if (canvas_item->_edit_use_rect()) { + Rect2 rect = canvas_item->_edit_get_rect(); + if (p_rect.has_point(xform.xform(rect.position)) && + p_rect.has_point(xform.xform(rect.position + Vector2(rect.size.x, 0))) && + p_rect.has_point(xform.xform(rect.position + Vector2(rect.size.x, rect.size.y))) && + p_rect.has_point(xform.xform(rect.position + Vector2(0, rect.size.y)))) { - r_items->push_back(canvas_item); + r_items->push_back(canvas_item); + } + } else { + if (p_rect.has_point(xform.xform(Point2()))) { + r_items->push_back(canvas_item); + } } } } @@ -575,8 +615,11 @@ void CanvasItemEditor::_save_canvas_item_state(List<CanvasItem *> p_canvas_items if (se) { se->undo_state = canvas_item->_edit_get_state(); se->pre_drag_xform = canvas_item->get_global_transform_with_canvas(); - se->pre_drag_rect = canvas_item->_edit_get_rect(); - + if (canvas_item->_edit_use_rect()) { + se->pre_drag_rect = canvas_item->_edit_get_rect(); + } else { + se->pre_drag_rect = Rect2(); + } se->pre_drag_bones_length = List<float>(); se->pre_drag_bones_undo_state = List<Dictionary>(); @@ -1290,54 +1333,55 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) { List<CanvasItem *> selection = _get_edited_canvas_items(); if (selection.size() == 1) { CanvasItem *canvas_item = selection[0]; + if (canvas_item->_edit_use_rect()) { + Rect2 rect = canvas_item->_edit_get_rect(); + Transform2D xform = transform * canvas_item->get_global_transform_with_canvas(); + + Vector2 endpoints[4] = { + xform.xform(rect.position), + xform.xform(rect.position + Vector2(rect.size.x, 0)), + xform.xform(rect.position + rect.size), + xform.xform(rect.position + Vector2(0, rect.size.y)) + }; - Rect2 rect = canvas_item->_edit_get_rect(); - Transform2D xform = transform * canvas_item->get_global_transform_with_canvas(); - - Vector2 endpoints[4] = { - xform.xform(rect.position), - xform.xform(rect.position + Vector2(rect.size.x, 0)), - xform.xform(rect.position + rect.size), - xform.xform(rect.position + Vector2(0, rect.size.y)) - }; - - DragType dragger[] = { - DRAG_TOP_LEFT, - DRAG_TOP, - DRAG_TOP_RIGHT, - DRAG_RIGHT, - DRAG_BOTTOM_RIGHT, - DRAG_BOTTOM, - DRAG_BOTTOM_LEFT, - DRAG_LEFT - }; - - DragType resize_drag = DRAG_NONE; - float radius = (select_handle->get_size().width / 2) * 1.5; - - for (int i = 0; i < 4; i++) { - int prev = (i + 3) % 4; - int next = (i + 1) % 4; + DragType dragger[] = { + DRAG_TOP_LEFT, + DRAG_TOP, + DRAG_TOP_RIGHT, + DRAG_RIGHT, + DRAG_BOTTOM_RIGHT, + DRAG_BOTTOM, + DRAG_BOTTOM_LEFT, + DRAG_LEFT + }; - Vector2 ofs = ((endpoints[i] - endpoints[prev]).normalized() + ((endpoints[i] - endpoints[next]).normalized())).normalized(); - ofs *= (select_handle->get_size().width / 2); - ofs += endpoints[i]; - if (ofs.distance_to(b->get_position()) < radius) - resize_drag = dragger[i * 2]; + DragType resize_drag = DRAG_NONE; + float radius = (select_handle->get_size().width / 2) * 1.5; - ofs = (endpoints[i] + endpoints[next]) / 2; - ofs += (endpoints[next] - endpoints[i]).tangent().normalized() * (select_handle->get_size().width / 2); - if (ofs.distance_to(b->get_position()) < radius) - resize_drag = dragger[i * 2 + 1]; - } + for (int i = 0; i < 4; i++) { + int prev = (i + 3) % 4; + int next = (i + 1) % 4; + + Vector2 ofs = ((endpoints[i] - endpoints[prev]).normalized() + ((endpoints[i] - endpoints[next]).normalized())).normalized(); + ofs *= (select_handle->get_size().width / 2); + ofs += endpoints[i]; + if (ofs.distance_to(b->get_position()) < radius) + resize_drag = dragger[i * 2]; + + ofs = (endpoints[i] + endpoints[next]) / 2; + ofs += (endpoints[next] - endpoints[i]).tangent().normalized() * (select_handle->get_size().width / 2); + if (ofs.distance_to(b->get_position()) < radius) + resize_drag = dragger[i * 2 + 1]; + } - if (resize_drag != DRAG_NONE) { - drag_type = resize_drag; - drag_from = transform.affine_inverse().xform(b->get_position()); - drag_selection = List<CanvasItem *>(); - drag_selection.push_back(canvas_item); - _save_canvas_item_state(drag_selection); - return true; + if (resize_drag != DRAG_NONE) { + drag_type = resize_drag; + drag_from = transform.affine_inverse().xform(b->get_position()); + drag_selection = List<CanvasItem *>(); + drag_selection.push_back(canvas_item); + _save_canvas_item_state(drag_selection); + return true; + } } } } @@ -1535,61 +1579,64 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { _save_canvas_item_state(drag_selection, true); } - _restore_canvas_item_state(drag_selection, true); - - bool move_local_base = k->get_alt(); - bool move_local_base_rotated = k->get_control() || k->get_metakey(); - - Vector2 dir; - if (k->get_scancode() == KEY_UP) - dir += Vector2(0, -1); - else if (k->get_scancode() == KEY_DOWN) - dir += Vector2(0, 1); - else if (k->get_scancode() == KEY_LEFT) - dir += Vector2(-1, 0); - else if (k->get_scancode() == KEY_RIGHT) - dir += Vector2(1, 0); - if (k->get_shift()) - dir *= grid_step * Math::pow(2.0, grid_step_multiplier); - - drag_to += dir; - if (k->get_shift()) - drag_to = drag_to.snapped(grid_step * Math::pow(2.0, grid_step_multiplier)); - - Point2 previous_pos; - if (drag_selection.size() == 1) { - Transform2D xform = drag_selection[0]->get_global_transform_with_canvas() * drag_selection[0]->get_transform().affine_inverse(); - previous_pos = xform.xform(drag_selection[0]->_edit_get_position()); - } else { - previous_pos = _get_encompassing_rect_from_list(drag_selection).position; - } + if (drag_selection.size() > 0) { - Point2 new_pos; - if (drag_selection.size() == 1) { - Node2D *node_2d = Object::cast_to<Node2D>(drag_selection[0]); - if (node_2d && move_local_base_rotated) { - Transform2D m; - m.rotate(node_2d->get_rotation()); - new_pos += m.xform(drag_to); - } else if (move_local_base) { - new_pos += drag_to; + _restore_canvas_item_state(drag_selection, true); + + bool move_local_base = k->get_alt(); + bool move_local_base_rotated = k->get_control() || k->get_metakey(); + + Vector2 dir; + if (k->get_scancode() == KEY_UP) + dir += Vector2(0, -1); + else if (k->get_scancode() == KEY_DOWN) + dir += Vector2(0, 1); + else if (k->get_scancode() == KEY_LEFT) + dir += Vector2(-1, 0); + else if (k->get_scancode() == KEY_RIGHT) + dir += Vector2(1, 0); + if (k->get_shift()) + dir *= grid_step * Math::pow(2.0, grid_step_multiplier); + + drag_to += dir; + if (k->get_shift()) + drag_to = drag_to.snapped(grid_step * Math::pow(2.0, grid_step_multiplier)); + + Point2 previous_pos; + if (drag_selection.size() == 1) { + Transform2D xform = drag_selection[0]->get_global_transform_with_canvas() * drag_selection[0]->get_transform().affine_inverse(); + previous_pos = xform.xform(drag_selection[0]->_edit_get_position()); + } else { + previous_pos = _get_encompassing_rect_from_list(drag_selection).position; + } + + Point2 new_pos; + if (drag_selection.size() == 1) { + Node2D *node_2d = Object::cast_to<Node2D>(drag_selection[0]); + if (node_2d && move_local_base_rotated) { + Transform2D m; + m.rotate(node_2d->get_rotation()); + new_pos += m.xform(drag_to); + } else if (move_local_base) { + new_pos += drag_to; + } else { + new_pos = previous_pos + (drag_to - drag_from); + } } else { new_pos = previous_pos + (drag_to - drag_from); } - } else { - new_pos = previous_pos + (drag_to - drag_from); - } - for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) { - CanvasItem *canvas_item = E->get(); - CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); - Transform2D xform = canvas_item->get_global_transform_with_canvas().affine_inverse() * canvas_item->get_transform(); + for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) { + CanvasItem *canvas_item = E->get(); + CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); + Transform2D xform = canvas_item->get_global_transform_with_canvas().affine_inverse() * canvas_item->get_transform(); - Node2D *node2d = Object::cast_to<Node2D>(canvas_item); - if (node2d && se->pre_drag_bones_undo_state.size() > 0) { - _solve_IK(node2d, new_pos); - } else { - canvas_item->_edit_set_position(canvas_item->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos)); + Node2D *node2d = Object::cast_to<Node2D>(canvas_item); + if (node2d && se->pre_drag_bones_undo_state.size() > 0) { + _solve_IK(node2d, new_pos); + } else { + canvas_item->_edit_set_position(canvas_item->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos)); + } } } return true; @@ -1648,6 +1695,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { return true; } else if (!selection_results.empty()) { + // Sorts items according the their z-index selection_results.sort(); NodePath root_path = get_tree()->get_edited_scene_root()->get_path(); @@ -1672,8 +1720,6 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { selection_menu_additive_selection = b->get_shift(); selection_menu->set_global_position(b->get_global_position()); selection_menu->popup(); - selection_menu->call_deferred("grab_click_focus"); - selection_menu->set_invalidate_click_until_motion(); return true; } } @@ -1709,16 +1755,17 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { canvas_item = selection[0].item; // Check if the canvas item is in a group, and select the group instead if it is the case - CanvasItem *canvas_item_tmp = canvas_item; - while (canvas_item_tmp) { - if (canvas_item->has_meta("_edit_group_")) { + Node *node = canvas_item; + while (node && node != scene) { + CanvasItem *canvas_item_tmp = Object::cast_to<CanvasItem>(node); + if (canvas_item_tmp && node->has_meta("_edit_group_")) { canvas_item = canvas_item_tmp; } - canvas_item_tmp = canvas_item_tmp->get_parent_item(); + node = node->get_parent(); } // Make sure the selected node is in the current scene - Node *node = canvas_item; + node = canvas_item; while (node && ((node != scene && node->get_owner() != scene) || !node->is_class("CanvasItem"))) { node = node->get_parent(); }; @@ -1768,7 +1815,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { if (bsfrom.y > bsto.y) SWAP(bsfrom.y, bsto.y); - _find_canvas_items_at_rect(Rect2(bsfrom, bsto - bsfrom), scene, &selitems); + _find_canvas_items_in_rect(Rect2(bsfrom, bsto - bsfrom), scene, &selitems); for (List<CanvasItem *>::Element *E = selitems.front(); E; E = E->next()) { editor_selection->add_node(E->get()); } @@ -1802,6 +1849,42 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { return false; } +bool CanvasItemEditor::_gui_input_hover(const Ref<InputEvent> &p_event) { + + Ref<InputEventMouseMotion> m = p_event; + if (m.is_valid()) { + if (drag_type == DRAG_NONE && tool == TOOL_SELECT) { + Point2 click = transform.affine_inverse().xform(m->get_position()); + Node *scene = editor->get_edited_scene(); + + //Checks if the hovered items changed, update the viewport if so + Vector<_SelectResult> hovering_results_tmp; + _find_canvas_items_at_pos(click, scene, hovering_results_tmp); + hovering_results_tmp.sort(); + bool changed = false; + if (hovering_results.size() == hovering_results_tmp.size()) { + for (int i = 0; i < hovering_results.size(); i++) { + if (hovering_results[i].item != hovering_results_tmp[i].item) { + changed = true; + break; + } + } + } else { + changed = true; + } + + if (changed) { + hovering_results = hovering_results_tmp; + viewport->update(); + } + + return true; + } + } + + return false; +} + void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) { bool accepted = false; if ((accepted = _gui_input_rulers_and_guides(p_event))) { @@ -1829,6 +1912,9 @@ void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) { if (accepted) accept_event(); + // Handles the mouse hovering + _gui_input_hover(p_event); + // Change the cursor CursorShape c = CURSOR_ARROW; switch (drag_type) { @@ -2095,6 +2181,9 @@ void CanvasItemEditor::_draw_grid() { void CanvasItemEditor::_draw_selection() { Ref<Texture> pivot_icon = get_icon("EditorPivot", "EditorIcons"); + Ref<Texture> position_icon = get_icon("EditorPosition", "EditorIcons"); + Ref<Texture> previous_position_icon = get_icon("EditorPositionPrevious", "EditorIcons"); + RID ci = viewport->get_canvas_item(); List<CanvasItem *> selection = _get_edited_canvas_items(false, false); @@ -2104,8 +2193,6 @@ void CanvasItemEditor::_draw_selection() { CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->get()); CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); - Rect2 rect = canvas_item->_edit_get_rect(); - // Draw the previous position if we are dragging the node if (show_helpers && (drag_type == DRAG_ALL || drag_type == DRAG_ROTATE || @@ -2114,42 +2201,52 @@ void CanvasItemEditor::_draw_selection() { const Transform2D pre_drag_xform = transform * se->pre_drag_xform; const Color pre_drag_color = Color(0.4, 0.6, 1, 0.7); - Vector2 pre_drag_endpoints[4] = { + if (canvas_item->_edit_use_rect()) { + Vector2 pre_drag_endpoints[4] = { - pre_drag_xform.xform(se->pre_drag_rect.position), - pre_drag_xform.xform(se->pre_drag_rect.position + Vector2(se->pre_drag_rect.size.x, 0)), - pre_drag_xform.xform(se->pre_drag_rect.position + se->pre_drag_rect.size), - pre_drag_xform.xform(se->pre_drag_rect.position + Vector2(0, se->pre_drag_rect.size.y)) - }; + pre_drag_xform.xform(se->pre_drag_rect.position), + pre_drag_xform.xform(se->pre_drag_rect.position + Vector2(se->pre_drag_rect.size.x, 0)), + pre_drag_xform.xform(se->pre_drag_rect.position + se->pre_drag_rect.size), + pre_drag_xform.xform(se->pre_drag_rect.position + Vector2(0, se->pre_drag_rect.size.y)) + }; - for (int i = 0; i < 4; i++) { - viewport->draw_line(pre_drag_endpoints[i], pre_drag_endpoints[(i + 1) % 4], pre_drag_color, 2); + for (int i = 0; i < 4; i++) { + viewport->draw_line(pre_drag_endpoints[i], pre_drag_endpoints[(i + 1) % 4], pre_drag_color, 2); + } + } else { + viewport->draw_texture(previous_position_icon, (pre_drag_xform.xform(Point2()) - (previous_position_icon->get_size() / 2)).floor()); } } Transform2D xform = transform * canvas_item->get_global_transform_with_canvas(); - VisualServer::get_singleton()->canvas_item_add_set_transform(ci, xform); - - // Draw the selected items surrounding boxes - Vector2 endpoints[4] = { - xform.xform(rect.position), - xform.xform(rect.position + Vector2(rect.size.x, 0)), - xform.xform(rect.position + rect.size), - xform.xform(rect.position + Vector2(0, rect.size.y)) - }; - Color c = Color(1, 0.6, 0.4, 0.7); + // Draw the selected items position / surrounding boxes + if (canvas_item->_edit_use_rect()) { + Rect2 rect = canvas_item->_edit_get_rect(); + Vector2 endpoints[4] = { + xform.xform(rect.position), + xform.xform(rect.position + Vector2(rect.size.x, 0)), + xform.xform(rect.position + rect.size), + xform.xform(rect.position + Vector2(0, rect.size.y)) + }; + + Color c = Color(1, 0.6, 0.4, 0.7); - VisualServer::get_singleton()->canvas_item_add_set_transform(ci, Transform2D()); + for (int i = 0; i < 4; i++) { + viewport->draw_line(endpoints[i], endpoints[(i + 1) % 4], c, 2); + } + } else { - for (int i = 0; i < 4; i++) { - viewport->draw_line(endpoints[i], endpoints[(i + 1) % 4], c, 2); + Transform2D transform = Transform2D(xform.get_rotation(), xform.get_origin()); + viewport->draw_set_transform_matrix(transform); + viewport->draw_texture(position_icon, -(position_icon->get_size() / 2)); + viewport->draw_set_transform_matrix(Transform2D()); } if (single && (tool == TOOL_SELECT || tool == TOOL_MOVE || tool == TOOL_ROTATE || tool == TOOL_EDIT_PIVOT)) { //kind of sucks // Draw the pivot if (canvas_item->_edit_get_pivot() != Vector2() || drag_type == DRAG_PIVOT || tool == TOOL_EDIT_PIVOT) { // This is not really clean :/ - viewport->draw_texture(pivot_icon, xform.xform(canvas_item->_edit_get_pivot()) + (-pivot_icon->get_size() / 2).floor()); + viewport->draw_texture(pivot_icon, (xform.xform(canvas_item->_edit_get_pivot()) - (pivot_icon->get_size() / 2)).floor()); } Control *control = Object::cast_to<Control>(canvas_item); @@ -2331,7 +2428,14 @@ void CanvasItemEditor::_draw_selection() { } } - if (tool == TOOL_SELECT) { + if (tool == TOOL_SELECT && canvas_item->_edit_use_rect()) { + Rect2 rect = canvas_item->_edit_get_rect(); + Vector2 endpoints[4] = { + xform.xform(rect.position), + xform.xform(rect.position + Vector2(rect.size.x, 0)), + xform.xform(rect.position + rect.size), + xform.xform(rect.position + Vector2(0, rect.size.y)) + }; for (int i = 0; i < 4; i++) { // Draw the resize handles int prev = (i + 3) % 4; @@ -2508,32 +2612,121 @@ void CanvasItemEditor::_draw_bones() { } } -void CanvasItemEditor::_draw_locks_and_groups(Node *p_node, const Transform2D &p_xform) { +void CanvasItemEditor::_draw_invisible_nodes_positions(Node *p_node, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) { + ERR_FAIL_COND(!p_node); + + Node *scene = editor->get_edited_scene(); + if (p_node != scene && p_node->get_owner() != scene && !scene->is_editable_instance(p_node)) + return; + CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); + if (canvas_item && !canvas_item->is_visible()) + return; + + Transform2D parent_xform = p_parent_xform; + Transform2D canvas_xform = p_canvas_xform; + + if (canvas_item && !canvas_item->is_set_as_toplevel()) { + parent_xform = parent_xform * canvas_item->get_transform(); + } else { + CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node); + parent_xform = Transform2D(); + canvas_xform = cl ? cl->get_transform() : p_canvas_xform; + } + + for (int i = p_node->get_child_count() - 1; i >= 0; i--) { + _draw_invisible_nodes_positions(p_node->get_child(i), parent_xform, canvas_xform); + } + + if (canvas_item && !canvas_item->_edit_use_rect() && !editor_selection->is_selected(canvas_item)) { + Transform2D xform = transform * canvas_xform * parent_xform; + + // Draw the node's position + Ref<Texture> position_icon = get_icon("EditorPositionUnselected", "EditorIcons"); + Transform2D transform = Transform2D(xform.get_rotation(), xform.get_origin()); + viewport->draw_set_transform_matrix(transform); + viewport->draw_texture(position_icon, -position_icon->get_size() / 2, Color(1.0, 1.0, 1.0, 0.5)); + viewport->draw_set_transform_matrix(Transform2D()); + } +} + +void CanvasItemEditor::_draw_hover() { + List<Rect2> previous_rects; + + for (int i = 0; i < hovering_results.size(); i++) { + // Draw the node's name and icon + CanvasItem *canvas_item = hovering_results[i].item; + + if (canvas_item->_edit_use_rect()) + continue; + + Transform2D xform = transform * canvas_item->get_global_transform_with_canvas(); + + // Get the resources + Ref<Texture> node_icon; + if (has_icon(canvas_item->get_class(), "EditorIcons")) + node_icon = get_icon(canvas_item->get_class(), "EditorIcons"); + else + node_icon = get_icon("Object", "EditorIcons"); + Ref<Font> font = get_font("font", "Label"); + String node_name = canvas_item->get_name(); + Size2 node_name_size = font->get_string_size(node_name); + Size2 item_size = Size2(node_icon->get_size().x + 4 + node_name_size.x, MAX(node_icon->get_size().y, node_name_size.y - 3)); + + Point2 pos = xform.get_origin() - Point2(0, item_size.y) + (Point2(node_icon->get_size().x, -node_icon->get_size().y) / 4); + // Rectify the position to avoid overlaping items + for (List<Rect2>::Element *E = previous_rects.front(); E; E = E->next()) { + if (E->get().intersects(Rect2(pos, item_size))) { + pos.y = E->get().get_position().y - item_size.y; + } + } + + previous_rects.push_back(Rect2(pos, item_size)); + + // Draw the node icon and name + viewport->draw_texture(node_icon, pos, Color(1.0, 1.0, 1.0, 0.5)); + viewport->draw_string(font, pos + Point2(node_icon->get_size().x + 4, item_size.y - 3), node_name, Color(1.0, 1.0, 1.0, 0.5)); + } +} + +void CanvasItemEditor::_draw_locks_and_groups(Node *p_node, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) { ERR_FAIL_COND(!p_node); - RID viewport_ci = viewport->get_canvas_item(); + Node *scene = editor->get_edited_scene(); + if (p_node != scene && p_node->get_owner() != scene && !scene->is_editable_instance(p_node->get_owner())) + return; + CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); + if (canvas_item && !canvas_item->is_visible()) + return; + + Transform2D parent_xform = p_parent_xform; + Transform2D canvas_xform = p_canvas_xform; - Transform2D transform_ci = p_xform; - CanvasItem *ci = Object::cast_to<CanvasItem>(p_node); - if (ci) - transform_ci = transform_ci * ci->get_transform(); + if (canvas_item && !canvas_item->is_set_as_toplevel()) { + parent_xform = parent_xform * canvas_item->get_transform(); + } else { + CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node); + parent_xform = Transform2D(); + canvas_xform = cl ? cl->get_transform() : p_canvas_xform; + } for (int i = p_node->get_child_count() - 1; i >= 0; i--) { - _draw_locks_and_groups(p_node->get_child(i), transform_ci); + _draw_locks_and_groups(p_node->get_child(i), parent_xform, canvas_xform); } - if (ci) { + RID viewport_canvas_item = viewport->get_canvas_item(); + if (canvas_item) { + float offset = 0; + Ref<Texture> lock = get_icon("LockViewport", "EditorIcons"); if (p_node->has_meta("_edit_lock_")) { - lock->draw(viewport_ci, transform_ci.xform(Point2(0, 0))); + lock->draw(viewport_canvas_item, (transform * canvas_xform * parent_xform).xform(Point2(0, 0)) + Point2(offset, 0)); + offset += lock->get_size().x; } Ref<Texture> group = get_icon("GroupViewport", "EditorIcons"); - if (ci->has_meta("_edit_group_")) { - Vector2 ofs = transform_ci.xform(Point2(0, 0)); - if (ci->has_meta("_edit_lock_")) - ofs = Point2(ofs.x + lock->get_size().x, ofs.y); - group->draw(viewport_ci, ofs); + if (canvas_item->has_meta("_edit_group_")) { + group->draw(viewport_canvas_item, (transform * canvas_xform * parent_xform).xform(Point2(0, 0)) + Point2(offset, 0)); + //offset += group->get_size().x; } } } @@ -2599,8 +2792,10 @@ void CanvasItemEditor::_draw_viewport() { _draw_grid(); _draw_selection(); _draw_axis(); - if (editor->get_edited_scene()) - _draw_locks_and_groups(editor->get_edited_scene(), transform); + if (editor->get_edited_scene()) { + _draw_locks_and_groups(editor->get_edited_scene()); + _draw_invisible_nodes_positions(editor->get_edited_scene()); + } RID ci = viewport->get_canvas_item(); VisualServer::get_singleton()->canvas_item_add_set_transform(ci, Transform2D()); @@ -2620,6 +2815,7 @@ void CanvasItemEditor::_draw_viewport() { if (show_guides) _draw_guides(); _draw_focus(); + _draw_hover(); } void CanvasItemEditor::_notification(int p_what) { @@ -2635,12 +2831,17 @@ void CanvasItemEditor::_notification(int p_what) { CanvasItem *canvas_item = E->get(); CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); - Rect2 r = canvas_item->_edit_get_rect(); + Rect2 rect; + if (canvas_item->_edit_use_rect()) { + rect = canvas_item->_edit_get_rect(); + } else { + rect = Rect2(); + } Transform2D xform = canvas_item->get_transform(); - if (r != se->prev_rect || xform != se->prev_xform) { + if (rect != se->prev_rect || xform != se->prev_xform) { viewport->update(); - se->prev_rect = r; + se->prev_rect = rect; se->prev_xform = xform; } @@ -2842,7 +3043,7 @@ void CanvasItemEditor::_update_scrollbars() { // Calculate scrollable area Rect2 canvas_item_rect = Rect2(Point2(), screen_rect); if (editor->get_edited_scene()) { - Rect2 content_rect = _get_scene_encompassing_rect(); + Rect2 content_rect = _get_encompassing_rect(editor->get_edited_scene()); canvas_item_rect.expand_to(content_rect.position); canvas_item_rect.expand_to(content_rect.position + content_rect.size); } @@ -3541,7 +3742,12 @@ void CanvasItemEditor::_focus_selection(int p_op) { //if (!canvas_item->is_visible_in_tree()) continue; ++count; - Rect2 item_rect = canvas_item->_edit_get_rect(); + Rect2 item_rect; + if (canvas_item->_edit_use_rect()) { + item_rect = canvas_item->_edit_get_rect(); + } else { + item_rect = Rect2(); + } Vector2 pos = canvas_item->get_global_transform().get_origin(); Vector2 scale = canvas_item->get_global_transform().get_scale(); diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index da7ce9172a..5ca8a37610 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -258,6 +258,7 @@ class CanvasItemEditor : public VBoxContainer { }; Vector<_SelectResult> selection_results; + Vector<_SelectResult> hovering_results; struct BoneList { @@ -336,7 +337,7 @@ class CanvasItemEditor : public VBoxContainer { Ref<ShortCut> divide_grid_step_shortcut; void _find_canvas_items_at_pos(const Point2 &p_pos, Node *p_node, Vector<_SelectResult> &r_items, int limit = 0, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D()); - void _find_canvas_items_at_rect(const Rect2 &p_rect, Node *p_node, List<CanvasItem *> *r_items, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D()); + void _find_canvas_items_in_rect(const Rect2 &p_rect, Node *p_node, List<CanvasItem *> *r_items, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D()); bool _select_click_on_item(CanvasItem *item, Point2 p_click_pos, bool p_append); ConfirmationDialog *snap_dialog; @@ -366,8 +367,8 @@ class CanvasItemEditor : public VBoxContainer { List<CanvasItem *> _get_edited_canvas_items(bool retreive_locked = false, bool remove_canvas_item_if_parent_in_selection = true); Rect2 _get_encompassing_rect_from_list(List<CanvasItem *> p_list); - void _expand_encompassing_rect_using_children(Rect2 &p_rect, Node *p_node, bool &r_first, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D()); - Rect2 _get_scene_encompassing_rect(); + void _expand_encompassing_rect_using_children(Rect2 &p_rect, const Node *p_node, bool &r_first, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D()); + Rect2 _get_encompassing_rect(const Node *p_node); Object *_get_editor_data(Object *p_what); @@ -387,7 +388,9 @@ class CanvasItemEditor : public VBoxContainer { void _draw_selection(); void _draw_axis(); void _draw_bones(); - void _draw_locks_and_groups(Node *p_node, const Transform2D &p_xform); + void _draw_invisible_nodes_positions(Node *p_node, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D()); + void _draw_locks_and_groups(Node *p_node, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D()); + void _draw_hover(); void _draw_viewport(); @@ -400,6 +403,7 @@ class CanvasItemEditor : public VBoxContainer { bool _gui_input_select(const Ref<InputEvent> &p_event); bool _gui_input_zoom_or_pan(const Ref<InputEvent> &p_event); bool _gui_input_rulers_and_guides(const Ref<InputEvent> &p_event); + bool _gui_input_hover(const Ref<InputEvent> &p_event); void _gui_input_viewport(const Ref<InputEvent> &p_event); diff --git a/editor/plugins/item_list_editor_plugin.cpp b/editor/plugins/item_list_editor_plugin.cpp index 5020f5b851..8b44f672b0 100644 --- a/editor/plugins/item_list_editor_plugin.cpp +++ b/editor/plugins/item_list_editor_plugin.cpp @@ -42,9 +42,18 @@ bool ItemListPlugin::_set(const StringName &p_name, const Variant &p_value) { set_item_text(idx, p_value); else if (what == "icon") set_item_icon(idx, p_value); - else if (what == "checkable") - set_item_checkable(idx, p_value); - else if (what == "checked") + else if (what == "checkable") { + // This keeps compatibility to/from versions where this property was a boolean, before radio buttons + switch ((int)p_value) { + case 0: + case 1: + set_item_checkable(idx, p_value); + break; + case 2: + set_item_radio_checkable(idx, true); + break; + } + } else if (what == "checked") set_item_checked(idx, p_value); else if (what == "id") set_item_id(idx, p_value); @@ -68,9 +77,14 @@ bool ItemListPlugin::_get(const StringName &p_name, Variant &r_ret) const { r_ret = get_item_text(idx); else if (what == "icon") r_ret = get_item_icon(idx); - else if (what == "checkable") - r_ret = is_item_checkable(idx); - else if (what == "checked") + else if (what == "checkable") { + // This keeps compatibility to/from versions where this property was a boolean, before radio buttons + if (!is_item_checkable(idx)) { + r_ret = 0; + } else { + r_ret = is_item_radio_checkable(idx) ? 2 : 1; + } + } else if (what == "checked") r_ret = is_item_checked(idx); else if (what == "id") r_ret = get_item_id(idx); @@ -95,7 +109,7 @@ void ItemListPlugin::_get_property_list(List<PropertyInfo> *p_list) const { int flags = get_flags(); if (flags & FLAG_CHECKABLE) { - p_list->push_back(PropertyInfo(Variant::BOOL, base + "checkable")); + p_list->push_back(PropertyInfo(Variant::BOOL, base + "checkable", PROPERTY_HINT_ENUM, "No,As checkbox,As radio button")); p_list->push_back(PropertyInfo(Variant::BOOL, base + "checked")); } diff --git a/editor/plugins/item_list_editor_plugin.h b/editor/plugins/item_list_editor_plugin.h index bd7d3db10d..d6a071b9b9 100644 --- a/editor/plugins/item_list_editor_plugin.h +++ b/editor/plugins/item_list_editor_plugin.h @@ -74,7 +74,9 @@ public: virtual Ref<Texture> get_item_icon(int p_idx) const { return Ref<Texture>(); }; virtual void set_item_checkable(int p_idx, bool p_check) {} + virtual void set_item_radio_checkable(int p_idx, bool p_check) {} virtual bool is_item_checkable(int p_idx) const { return false; }; + virtual bool is_item_radio_checkable(int p_idx) const { return false; }; virtual void set_item_checked(int p_idx, bool p_checked) {} virtual bool is_item_checked(int p_idx) const { return false; }; @@ -145,7 +147,9 @@ public: virtual Ref<Texture> get_item_icon(int p_idx) const { return pp->get_item_icon(p_idx); } virtual void set_item_checkable(int p_idx, bool p_check) { pp->set_item_as_checkable(p_idx, p_check); } + virtual void set_item_radio_checkable(int p_idx, bool p_check) { pp->set_item_as_radio_checkable(p_idx, p_check); } virtual bool is_item_checkable(int p_idx) const { return pp->is_item_checkable(p_idx); } + virtual bool is_item_radio_checkable(int p_idx) const { return pp->is_item_radio_checkable(p_idx); } virtual void set_item_checked(int p_idx, bool p_checked) { pp->set_item_checked(p_idx, p_checked); } virtual bool is_item_checked(int p_idx) const { return pp->is_item_checked(p_idx); } diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 2ce36ee8d5..9193b3fbbf 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -39,9 +39,11 @@ #include "core/project_settings.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" +#include "editor/find_in_files.h" #include "editor/node_dock.h" #include "editor/script_editor_debugger.h" #include "scene/main/viewport.h" +#include "script_text_editor.h" /*** SCRIPT EDITOR ****/ @@ -54,6 +56,8 @@ void ScriptEditorBase::_bind_methods() { ADD_SIGNAL(MethodInfo("request_open_script_at_line", PropertyInfo(Variant::OBJECT, "script"), PropertyInfo(Variant::INT, "line"))); ADD_SIGNAL(MethodInfo("request_save_history")); ADD_SIGNAL(MethodInfo("go_to_help", PropertyInfo(Variant::STRING, "what"))); + // TODO This signal is no use for VisualScript... + ADD_SIGNAL(MethodInfo("search_in_files_requested", PropertyInfo(Variant::STRING, "text"))); } static bool _can_open_in_editor(Script *p_script) { @@ -298,15 +302,9 @@ void ScriptEditor::_script_created(Ref<Script> p_script) { void ScriptEditor::_goto_script_line2(int p_line) { - int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) - return; - - ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); - if (!current) - return; - - current->goto_line(p_line); + ScriptEditorBase *current = _get_current_editor(); + if (current) + current->goto_line(p_line); } void ScriptEditor::_goto_script_line(REF p_script, int p_line) { @@ -316,19 +314,22 @@ void ScriptEditor::_goto_script_line(REF p_script, int p_line) { if (edit(p_script, p_line, 0)) { editor->push_item(p_script.ptr()); - int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) - return; - - ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); - if (!current) - return; - - current->goto_line(p_line, true); + ScriptEditorBase *current = _get_current_editor(); + if (current) + current->goto_line(p_line, true); } } } +ScriptEditorBase *ScriptEditor::_get_current_editor() const { + + int selected = tab_container->get_current_tab(); + if (selected < 0 || selected >= tab_container->get_child_count()) + return NULL; + + return Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); +} + void ScriptEditor::_update_history_arrows() { script_back->set_disabled(history_pos <= 0); @@ -587,7 +588,7 @@ void ScriptEditor::_close_docs_tab() { } void ScriptEditor::_copy_script_path() { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(tab_container->get_current_tab())); + ScriptEditorBase *se = _get_current_editor(); Ref<Script> script = se->get_edited_script(); OS::get_singleton()->set_clipboard(script->get_path()); } @@ -819,11 +820,8 @@ void ScriptEditor::_file_dialog_action(String p_file) { Ref<Script> ScriptEditor::_get_current_script() { - int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) - return NULL; + ScriptEditorBase *current = _get_current_editor(); - ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); if (current) { return current->get_edited_script(); } else { @@ -939,11 +937,7 @@ void ScriptEditor::_menu_option(int p_option) { } } - int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) - return; - - ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); + ScriptEditorBase *current = _get_current_editor(); if (current) { switch (p_option) { @@ -1033,7 +1027,7 @@ void ScriptEditor::_menu_option(int p_option) { _copy_script_path(); } break; case SHOW_IN_FILE_SYSTEM: { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(tab_container->get_current_tab())); + ScriptEditorBase *se = _get_current_editor(); Ref<Script> script = se->get_edited_script(); FileSystemDock *file_system_dock = EditorNode::get_singleton()->get_filesystem_dock(); file_system_dock->navigate_to_path(script->get_path()); @@ -1223,6 +1217,17 @@ void ScriptEditor::_notification(int p_what) { recent_scripts->set_as_minsize(); } break; + case CanvasItem::NOTIFICATION_VISIBILITY_CHANGED: { + + if (is_visible()) { + find_in_files_button->show(); + } else { + find_in_files->hide(); + find_in_files_button->hide(); + } + + } break; + default: break; } @@ -1230,15 +1235,11 @@ void ScriptEditor::_notification(int p_what) { bool ScriptEditor::can_take_away_focus() const { - int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) - return true; - - ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); - if (!current) + ScriptEditorBase *current = _get_current_editor(); + if (current) + return current->can_lose_focus_on_node_selection(); + else return true; - - return current->can_lose_focus_on_node_selection(); } void ScriptEditor::close_builtin_scripts_from_scene(const String &p_scene) { @@ -1315,20 +1316,13 @@ void ScriptEditor::ensure_focus_current() { if (!is_inside_tree()) return; - int cidx = tab_container->get_current_tab(); - if (cidx < 0 || cidx >= tab_container->get_tab_count()) - return; - - Control *c = Object::cast_to<Control>(tab_container->get_child(cidx)); - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(c); - if (!se) - return; - se->ensure_focus(); + ScriptEditorBase *current = _get_current_editor(); + if (current) + current->ensure_focus(); } void ScriptEditor::_members_overview_selected(int p_idx) { - Node *current = tab_container->get_child(tab_container->get_current_tab()); - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(current); + ScriptEditorBase *se = _get_current_editor(); if (!se) { return; } @@ -1362,18 +1356,12 @@ void ScriptEditor::ensure_select_current() { if (tab_container->get_child_count() && tab_container->get_current_tab() >= 0) { - Node *current = tab_container->get_child(tab_container->get_current_tab()); - - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(current); + ScriptEditorBase *se = _get_current_editor(); if (se) { - Ref<Script> script = se->get_edited_script(); - if (!grab_focus_block && is_visible_in_tree()) se->ensure_focus(); } - - EditorHelp *eh = Object::cast_to<EditorHelp>(current); } _update_selected_editor_menu(); @@ -1413,12 +1401,7 @@ struct _ScriptEditorItemData { void ScriptEditor::_update_members_overview_visibility() { - int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) - return; - - Node *current = tab_container->get_child(tab_container->get_current_tab()); - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(current); + ScriptEditorBase *se = _get_current_editor(); if (!se) { members_overview->set_visible(false); return; @@ -1434,12 +1417,7 @@ void ScriptEditor::_update_members_overview_visibility() { void ScriptEditor::_update_members_overview() { members_overview->clear(); - int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) - return; - - Node *current = tab_container->get_child(tab_container->get_current_tab()); - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(current); + ScriptEditorBase *se = _get_current_editor(); if (!se) { return; } @@ -1813,6 +1791,7 @@ bool ScriptEditor::edit(const Ref<Script> &p_script, int p_line, int p_col, bool se->connect("request_open_script_at_line", this, "_goto_script_line"); se->connect("go_to_help", this, "_help_class_goto"); se->connect("request_save_history", this, "_save_history"); + se->connect("search_in_files_requested", this, "_on_find_in_files_requested"); //test for modification, maybe the script was not edited but was loaded @@ -2530,6 +2509,48 @@ void ScriptEditor::_script_changed() { NodeDock::singleton->update_lists(); } +void ScriptEditor::_on_find_in_files_requested(String text) { + + find_in_files_dialog->set_search_text(text); + find_in_files_dialog->popup_centered_minsize(); +} + +void ScriptEditor::_on_find_in_files_result_selected(String fpath, int line_number, int begin, int end) { + + Ref<Resource> res = ResourceLoader::load(fpath); + edit(res); + + ScriptEditorBase *seb = _get_current_editor(); + + ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(seb); + if (ste) { + ste->goto_line_selection(line_number - 1, begin, end); + } +} + +void ScriptEditor::_start_find_in_files(bool with_replace) { + + FindInFiles *f = find_in_files->get_finder(); + + f->set_search_text(find_in_files_dialog->get_search_text()); + f->set_match_case(find_in_files_dialog->is_match_case()); + f->set_whole_words(find_in_files_dialog->is_match_case()); + f->set_folder(find_in_files_dialog->get_folder()); + f->set_filter(find_in_files_dialog->get_filter()); + + find_in_files->set_with_replace(with_replace); + find_in_files->start_search(); + + find_in_files_button->set_pressed(true); + find_in_files->show(); +} + +void ScriptEditor::_on_find_in_files_modified_files(PoolStringArray paths) { + + _test_script_times_on_disk(); + _update_modified_scripts_for_external_editor(); +} + void ScriptEditor::_bind_methods() { ClassDB::bind_method("_file_dialog_action", &ScriptEditor::_file_dialog_action); @@ -2577,6 +2598,10 @@ void ScriptEditor::_bind_methods() { ClassDB::bind_method("_script_list_gui_input", &ScriptEditor::_script_list_gui_input); ClassDB::bind_method("_script_changed", &ScriptEditor::_script_changed); ClassDB::bind_method("_update_recent_scripts", &ScriptEditor::_update_recent_scripts); + ClassDB::bind_method("_on_find_in_files_requested", &ScriptEditor::_on_find_in_files_requested); + ClassDB::bind_method("_start_find_in_files", &ScriptEditor::_start_find_in_files); + ClassDB::bind_method("_on_find_in_files_result_selected", &ScriptEditor::_on_find_in_files_result_selected); + ClassDB::bind_method("_on_find_in_files_modified_files", &ScriptEditor::_on_find_in_files_modified_files); ClassDB::bind_method(D_METHOD("get_drag_data_fw", "point", "from"), &ScriptEditor::get_drag_data_fw); ClassDB::bind_method(D_METHOD("can_drop_data_fw", "point", "data", "from"), &ScriptEditor::can_drop_data_fw); @@ -2838,6 +2863,19 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { add_child(help_index); help_index->connect("open_class", this, "_help_class_open"); + find_in_files_dialog = memnew(FindInFilesDialog); + find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_FIND_REQUESTED, this, "_start_find_in_files", varray(false)); + find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_REPLACE_REQUESTED, this, "_start_find_in_files", varray(true)); + add_child(find_in_files_dialog); + find_in_files = memnew(FindInFilesPanel); + find_in_files_button = editor->add_bottom_panel_item(TTR("Search results"), find_in_files); + find_in_files_button->set_tooltip(TTR("Search in files")); + find_in_files->set_custom_minimum_size(Size2(0, 200)); + find_in_files->connect(FindInFilesPanel::SIGNAL_RESULT_SELECTED, this, "_on_find_in_files_result_selected"); + find_in_files->connect(FindInFilesPanel::SIGNAL_FILES_MODIFIED, this, "_on_find_in_files_modified_files"); + find_in_files->hide(); + find_in_files_button->hide(); + history_pos = -1; //debugger_gui->hide(); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index f947351089..9f37b18d7d 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -119,6 +119,8 @@ typedef SyntaxHighlighter *(*CreateSyntaxHighlighterFunc)(); typedef ScriptEditorBase *(*CreateScriptEditorFunc)(const Ref<Script> &p_script); class EditorScriptCodeCompletionCache; +class FindInFilesDialog; +class FindInFilesPanel; class ScriptEditor : public PanelContainer { @@ -217,6 +219,10 @@ class ScriptEditor : public PanelContainer { ToolButton *script_back; ToolButton *script_forward; + FindInFilesDialog *find_in_files_dialog; + FindInFilesPanel *find_in_files; + Button *find_in_files_button; + enum { SCRIPT_EDITOR_FUNC_MAX = 32, SYNTAX_HIGHLIGHTER_FUNC_MAX = 32 @@ -304,6 +310,8 @@ class ScriptEditor : public PanelContainer { void _update_window_menu(); void _script_created(Ref<Script> p_script); + ScriptEditorBase *_get_current_editor() const; + void _save_layout(); void _editor_settings_changed(); void _autosave_scripts(); @@ -359,6 +367,11 @@ class ScriptEditor : public PanelContainer { Ref<Script> _get_current_script(); Array _get_open_scripts() const; + void _on_find_in_files_requested(String text); + void _on_find_in_files_result_selected(String fpath, int line_number, int begin, int end); + void _start_find_in_files(bool with_replace); + void _on_find_in_files_modified_files(PoolStringArray paths); + static void _open_script_request(const String &p_path); static ScriptEditor *script_editor; diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 711a313902..bcc575a7ac 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -529,6 +529,14 @@ void ScriptTextEditor::goto_line(int p_line, bool p_with_error) { tx->call_deferred("cursor_set_line", p_line); } +void ScriptTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) { + TextEdit *tx = code_editor->get_text_edit(); + tx->unfold_line(p_line); + tx->call_deferred("cursor_set_line", p_line); + tx->call_deferred("cursor_set_column", p_begin); + tx->select(p_line, p_begin, p_line, p_end); +} + void ScriptTextEditor::ensure_focus() { code_editor->get_text_edit()->grab_focus(); @@ -1173,6 +1181,15 @@ void ScriptTextEditor::_edit_option(int p_op) { code_editor->get_find_replace_bar()->popup_replace(); } break; + case SEARCH_IN_FILES: { + + String selected_text = code_editor->get_text_edit()->get_selection_text(); + + // Yep, because it doesn't make sense to instance this dialog for every single script open... + // So this will be delegated to the ScriptEditor + emit_signal("search_in_files_requested", selected_text); + + } break; case SEARCH_LOCATE_FUNCTION: { quick_open->popup(get_functions()); @@ -1660,6 +1677,8 @@ ScriptTextEditor::ScriptTextEditor() { search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV); search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE); search_menu->get_popup()->add_separator(); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_in_files"), SEARCH_IN_FILES); + search_menu->get_popup()->add_separator(); search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_function"), SEARCH_LOCATE_FUNCTION); search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE); search_menu->get_popup()->add_separator(); @@ -1739,7 +1758,9 @@ void ScriptTextEditor::register_editor() { ED_SHORTCUT("script_text_editor/find_previous", TTR("Find Previous"), KEY_MASK_SHIFT | KEY_F3); ED_SHORTCUT("script_text_editor/replace", TTR("Replace.."), KEY_MASK_CMD | KEY_R); - ED_SHORTCUT("script_text_editor/goto_function", TTR("Goto Function.."), KEY_MASK_SHIFT | KEY_MASK_CMD | KEY_F); + ED_SHORTCUT("script_text_editor/find_in_files", TTR("Find in files..."), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_F); + + ED_SHORTCUT("script_text_editor/goto_function", TTR("Goto Function.."), KEY_MASK_ALT | KEY_MASK_CMD | KEY_F); ED_SHORTCUT("script_text_editor/goto_line", TTR("Goto Line.."), KEY_MASK_CMD | KEY_L); ED_SHORTCUT("script_text_editor/contextual_help", TTR("Contextual Help"), KEY_MASK_SHIFT | KEY_F1); diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index eb52d2593a..a93e1a6fa8 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -106,6 +106,7 @@ class ScriptTextEditor : public ScriptEditorBase { SEARCH_REPLACE, SEARCH_LOCATE_FUNCTION, SEARCH_GOTO_LINE, + SEARCH_IN_FILES, DEBUG_TOGGLE_BREAKPOINT, DEBUG_REMOVE_ALL_BREAKPOINTS, DEBUG_GOTO_NEXT_BREAKPOINT, @@ -170,6 +171,7 @@ public: virtual void tag_saved_version(); virtual void goto_line(int p_line, bool p_with_error = false); + void goto_line_selection(int p_line, int p_begin, int p_end); virtual void reload(bool p_soft); virtual void get_breakpoints(List<int> *p_breakpoints); diff --git a/editor/plugins/shader_graph_editor_plugin.cpp b/editor/plugins/shader_graph_editor_plugin.cpp index 59085c203f..e1d28cc215 100644 --- a/editor/plugins/shader_graph_editor_plugin.cpp +++ b/editor/plugins/shader_graph_editor_plugin.cpp @@ -2769,8 +2769,6 @@ void ShaderGraphEditor::_popup_requested(const Vector2 &p_position) popup->set_global_position(p_position); popup->set_size( Size2( 200, 0) ); popup->popup(); - popup->call_deferred("grab_click_focus"); - popup->set_invalidate_click_until_motion(); } void ShaderGraphEditor::_notification(int p_what) { diff --git a/editor/plugins/spatial_editor_plugin.cpp b/editor/plugins/spatial_editor_plugin.cpp index e484140527..9d7c582e0e 100644 --- a/editor/plugins/spatial_editor_plugin.cpp +++ b/editor/plugins/spatial_editor_plugin.cpp @@ -886,8 +886,6 @@ void SpatialEditorViewport::_list_select(Ref<InputEventMouseButton> b) { selection_menu->set_global_position(b->get_global_position()); selection_menu->popup(); - selection_menu->call_deferred("grab_click_focus"); - selection_menu->set_invalidate_click_until_motion(); } } @@ -3353,14 +3351,14 @@ SpatialEditorViewport::SpatialEditorViewport(SpatialEditor *p_spatial_editor, Ed view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/front_view"), VIEW_FRONT); view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/rear_view"), VIEW_REAR); view_menu->get_popup()->add_separator(); - view_menu->get_popup()->add_check_item(TTR("Perspective") + " (" + ED_GET_SHORTCUT("spatial_editor/switch_perspective_orthogonal")->get_as_text() + ")", VIEW_PERSPECTIVE); - view_menu->get_popup()->add_check_item(TTR("Orthogonal") + " (" + ED_GET_SHORTCUT("spatial_editor/switch_perspective_orthogonal")->get_as_text() + ")", VIEW_ORTHOGONAL); + view_menu->get_popup()->add_radio_check_item(TTR("Perspective") + " (" + ED_GET_SHORTCUT("spatial_editor/switch_perspective_orthogonal")->get_as_text() + ")", VIEW_PERSPECTIVE); + view_menu->get_popup()->add_radio_check_item(TTR("Orthogonal") + " (" + ED_GET_SHORTCUT("spatial_editor/switch_perspective_orthogonal")->get_as_text() + ")", VIEW_ORTHOGONAL); view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_PERSPECTIVE), true); view_menu->get_popup()->add_separator(); - view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_normal", TTR("Display Normal")), VIEW_DISPLAY_NORMAL); - view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_wireframe", TTR("Display Wireframe")), VIEW_DISPLAY_WIREFRAME); - view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_overdraw", TTR("Display Overdraw")), VIEW_DISPLAY_OVERDRAW); - view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_unshaded", TTR("Display Unshaded")), VIEW_DISPLAY_SHADELESS); + view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_normal", TTR("Display Normal")), VIEW_DISPLAY_NORMAL); + view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_wireframe", TTR("Display Wireframe")), VIEW_DISPLAY_WIREFRAME); + view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_overdraw", TTR("Display Overdraw")), VIEW_DISPLAY_OVERDRAW); + view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_unshaded", TTR("Display Unshaded")), VIEW_DISPLAY_SHADELESS); view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL), true); view_menu->get_popup()->add_separator(); view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_environment", TTR("View Environment")), VIEW_ENVIRONMENT); @@ -5111,12 +5109,12 @@ SpatialEditor::SpatialEditor(EditorNode *p_editor) { accept = memnew(AcceptDialog); editor->get_gui_base()->add_child(accept); - p->add_check_shortcut(ED_SHORTCUT("spatial_editor/1_viewport", TTR("1 Viewport"), KEY_MASK_CMD + KEY_1), MENU_VIEW_USE_1_VIEWPORT); - p->add_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports", TTR("2 Viewports"), KEY_MASK_CMD + KEY_2), MENU_VIEW_USE_2_VIEWPORTS); - p->add_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports_alt", TTR("2 Viewports (Alt)"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_2), MENU_VIEW_USE_2_VIEWPORTS_ALT); - p->add_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports", TTR("3 Viewports"), KEY_MASK_CMD + KEY_3), MENU_VIEW_USE_3_VIEWPORTS); - p->add_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports_alt", TTR("3 Viewports (Alt)"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_3), MENU_VIEW_USE_3_VIEWPORTS_ALT); - p->add_check_shortcut(ED_SHORTCUT("spatial_editor/4_viewports", TTR("4 Viewports"), KEY_MASK_CMD + KEY_4), MENU_VIEW_USE_4_VIEWPORTS); + p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/1_viewport", TTR("1 Viewport"), KEY_MASK_CMD + KEY_1), MENU_VIEW_USE_1_VIEWPORT); + p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports", TTR("2 Viewports"), KEY_MASK_CMD + KEY_2), MENU_VIEW_USE_2_VIEWPORTS); + p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports_alt", TTR("2 Viewports (Alt)"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_2), MENU_VIEW_USE_2_VIEWPORTS_ALT); + p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports", TTR("3 Viewports"), KEY_MASK_CMD + KEY_3), MENU_VIEW_USE_3_VIEWPORTS); + p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports_alt", TTR("3 Viewports (Alt)"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_3), MENU_VIEW_USE_3_VIEWPORTS_ALT); + p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/4_viewports", TTR("4 Viewports"), KEY_MASK_CMD + KEY_4), MENU_VIEW_USE_4_VIEWPORTS); p->add_separator(); p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_origin", TTR("View Origin")), MENU_VIEW_ORIGIN); diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp index 4367fe8976..5ba3931689 100644 --- a/editor/plugins/texture_region_editor_plugin.cpp +++ b/editor/plugins/texture_region_editor_plugin.cpp @@ -805,12 +805,10 @@ TextureRegionEditor::TextureRegionEditor(EditorNode *p_editor) { snap_mode_button->set_text(TTR("<None>")); PopupMenu *p = snap_mode_button->get_popup(); p->set_hide_on_checkable_item_selection(false); - p->add_item(TTR("<None>"), 0); - p->add_item(TTR("Pixel Snap"), 1); - p->add_item(TTR("Grid Snap"), 2); - p->add_item(TTR("Auto Slice"), 3); - for (int i = 0; i < 4; i++) - p->set_item_as_checkable(i, true); + p->add_radio_check_item(TTR("<None>"), 0); + p->add_radio_check_item(TTR("Pixel Snap"), 1); + p->add_radio_check_item(TTR("Grid Snap"), 2); + p->add_radio_check_item(TTR("Auto Slice"), 3); p->set_item_checked(0, true); p->connect("id_pressed", this, "_set_snap_mode"); hb_grid = memnew(HBoxContainer); diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 295d9439ad..550dfb3ae1 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -692,6 +692,10 @@ ThemeEditor::ThemeEditor() { test_menu_button->get_popup()->add_check_item(TTR("Check Item")); test_menu_button->get_popup()->add_check_item(TTR("Checked Item")); test_menu_button->get_popup()->set_item_checked(2, true); + test_menu_button->get_popup()->add_separator(); + test_menu_button->get_popup()->add_check_item(TTR("Radio Item")); + test_menu_button->get_popup()->add_radio_check_item(TTR("Checked Radio Item")); + test_menu_button->get_popup()->set_item_checked(5, true); first_vb->add_child(test_menu_button); OptionButton *test_option_button = memnew(OptionButton); diff --git a/editor/plugins/tile_set_editor_plugin.cpp b/editor/plugins/tile_set_editor_plugin.cpp index 2311439728..41692e805f 100644 --- a/editor/plugins/tile_set_editor_plugin.cpp +++ b/editor/plugins/tile_set_editor_plugin.cpp @@ -278,10 +278,11 @@ void TileSetEditor::_changed_callback(Object *p_changed, const char *p_prop) { preview->set_region_rect(tileset->tile_get_region(get_current_tile())); } else if (p_prop == StringName("name")) { update_tile_list_icon(); - } else if (p_prop == StringName("texture") || p_prop == StringName("tile_mode")) { + } else if (p_prop == StringName("texture") || p_prop == StringName("modulate") || p_prop == StringName("tile_mode")) { _on_tile_list_selected(get_current_tile()); workspace->update(); preview->set_texture(tileset->tile_get_texture(get_current_tile())); + preview->set_modulate(tileset->tile_get_modulate(get_current_tile())); preview->set_region_rect(tileset->tile_get_region(get_current_tile())); if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE) property_editor->show(); @@ -578,6 +579,7 @@ void TileSetEditor::_on_tile_list_selected(int p_index) { if (get_current_tile() >= 0) { current_item_index = p_index; preview->set_texture(tileset->tile_get_texture(get_current_tile())); + preview->set_modulate(tileset->tile_get_modulate(get_current_tile())); preview->set_region_rect(tileset->tile_get_region(get_current_tile())); workspace->set_custom_minimum_size(tileset->tile_get_region(get_current_tile()).size); update_workspace_tile_mode(); @@ -1736,6 +1738,7 @@ void TileSetEditor::update_tile_list() { region.position += pos; } tile_list->set_item_icon_region(tile_list->get_item_count() - 1, region); + tile_list->set_item_icon_modulate(tile_list->get_item_count() - 1, tileset->tile_get_modulate(E->get())); } if (tile_list->get_item_count() > 0 && selected_tile < tile_list->get_item_count()) { tile_list->select(selected_tile); @@ -1763,6 +1766,7 @@ void TileSetEditor::update_tile_list_icon() { tile_list->set_item_metadata(current_idx, E->get()); tile_list->set_item_icon(current_idx, tileset->tile_get_texture(E->get())); tile_list->set_item_icon_region(current_idx, region); + tile_list->set_item_icon_modulate(current_idx, tileset->tile_get_modulate(E->get())); tile_list->set_item_text(current_idx, tileset->tile_get_name(E->get())); current_idx += 1; } diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index 75523cd843..6e3be343ba 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -31,6 +31,7 @@ #include "project_settings_editor.h" #include "core/global_constants.h" +#include "core/input_map.h" #include "core/os/keyboard.h" #include "core/project_settings.h" #include "core/translation.h" @@ -212,7 +213,7 @@ void ProjectSettingsEditor::_device_input_add() { Ref<InputEventMouseButton> mb; mb.instance(); mb->set_button_index(device_index->get_selected() + 1); - mb->set_device(device_id->get_value()); + mb->set_device(_get_current_device()); for (int i = 0; i < arr.size(); i++) { @@ -233,7 +234,7 @@ void ProjectSettingsEditor::_device_input_add() { jm.instance(); jm->set_axis(device_index->get_selected() >> 1); jm->set_axis_value(device_index->get_selected() & 1 ? 1 : -1); - jm->set_device(device_id->get_value()); + jm->set_device(_get_current_device()); for (int i = 0; i < arr.size(); i++) { @@ -254,7 +255,7 @@ void ProjectSettingsEditor::_device_input_add() { jb.instance(); jb->set_button_index(device_index->get_selected()); - jb->set_device(device_id->get_value()); + jb->set_device(_get_current_device()); for (int i = 0; i < arr.size(); i++) { @@ -289,6 +290,20 @@ void ProjectSettingsEditor::_device_input_add() { _show_last_added(ie, name); } +void ProjectSettingsEditor::_set_current_device(int i_device) { + device_id->select(i_device + 1); +} + +int ProjectSettingsEditor::_get_current_device() { + return device_id->get_selected() - 1; +} + +String ProjectSettingsEditor::_get_device_string(int i_device) { + if (i_device == InputMap::ALL_DEVICES) + return TTR("All Devices"); + return TTR("Device") + " " + itos(i_device); +} + void ProjectSettingsEditor::_press_a_key_confirm() { if (last_wait_for_key.is_null()) @@ -422,10 +437,10 @@ void ProjectSettingsEditor::_add_item(int p_item, Ref<InputEvent> p_exiting_even Ref<InputEventMouseButton> mb = p_exiting_event; if (mb.is_valid()) { device_index->select(mb->get_button_index() - 1); - device_id->set_value(mb->get_device()); + _set_current_device(mb->get_device()); device_input->get_ok()->set_text(TTR("Change")); } else { - device_id->set_value(0); + _set_current_device(0); device_input->get_ok()->set_text(TTR("Add")); } } break; @@ -443,10 +458,10 @@ void ProjectSettingsEditor::_add_item(int p_item, Ref<InputEvent> p_exiting_even Ref<InputEventJoypadMotion> jm = p_exiting_event; if (jm.is_valid()) { device_index->select(jm->get_axis() * 2 + (jm->get_axis_value() > 0 ? 1 : 0)); - device_id->set_value(jm->get_device()); + _set_current_device(jm->get_device()); device_input->get_ok()->set_text(TTR("Change")); } else { - device_id->set_value(0); + _set_current_device(0); device_input->get_ok()->set_text(TTR("Add")); } } break; @@ -464,10 +479,10 @@ void ProjectSettingsEditor::_add_item(int p_item, Ref<InputEvent> p_exiting_even Ref<InputEventJoypadButton> jb = p_exiting_event; if (jb.is_valid()) { device_index->select(jb->get_button_index()); - device_id->set_value(jb->get_device()); + _set_current_device(jb->get_device()); device_input->get_ok()->set_text(TTR("Change")); } else { - device_id->set_value(0); + _set_current_device(0); device_input->get_ok()->set_text(TTR("Add")); } @@ -676,7 +691,7 @@ void ProjectSettingsEditor::_update_actions() { if (jb.is_valid()) { - String str = TTR("Device") + " " + itos(jb->get_device()) + ", " + TTR("Button") + " " + itos(jb->get_button_index()); + String str = _get_device_string(jb->get_device()) + ", " + TTR("Button") + " " + itos(jb->get_button_index()); if (jb->get_button_index() >= 0 && jb->get_button_index() < JOY_BUTTON_MAX) str += String() + " (" + _button_names[jb->get_button_index()] + ")."; else @@ -689,7 +704,7 @@ void ProjectSettingsEditor::_update_actions() { Ref<InputEventMouseButton> mb = ie; if (mb.is_valid()) { - String str = TTR("Device") + " " + itos(mb->get_device()) + ", "; + String str = _get_device_string(mb->get_device()) + ", "; switch (mb->get_button_index()) { case BUTTON_LEFT: str += TTR("Left Button."); break; case BUTTON_RIGHT: str += TTR("Right Button."); break; @@ -710,7 +725,7 @@ void ProjectSettingsEditor::_update_actions() { int ax = jm->get_axis(); int n = 2 * ax + (jm->get_axis_value() < 0 ? 0 : 1); String desc = _axis_names[n]; - String str = TTR("Device") + " " + itos(jm->get_device()) + ", " + TTR("Axis") + " " + itos(ax) + " " + (jm->get_axis_value() < 0 ? "-" : "+") + desc + "."; + String str = _get_device_string(jm->get_device()) + ", " + TTR("Axis") + " " + itos(ax) + " " + (jm->get_axis_value() < 0 ? "-" : "+") + desc + "."; action->set_text(0, str); action->set_icon(0, get_icon("JoyAxis", "EditorIcons")); } @@ -1781,8 +1796,10 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { l->set_text(TTR("Device:")); vbc_left->add_child(l); - device_id = memnew(SpinBox); - device_id->set_value(0); + device_id = memnew(OptionButton); + for (int i = -1; i < 8; i++) + device_id->add_item(_get_device_string(i)); + _set_current_device(0); vbc_left->add_child(device_id); VBoxContainer *vbc_right = memnew(VBoxContainer); diff --git a/editor/project_settings_editor.h b/editor/project_settings_editor.h index 9364b0ff2c..0ced88d7f6 100644 --- a/editor/project_settings_editor.h +++ b/editor/project_settings_editor.h @@ -80,7 +80,7 @@ class ProjectSettingsEditor : public AcceptDialog { ConfirmationDialog *press_a_key; Label *press_a_key_label; ConfirmationDialog *device_input; - SpinBox *device_id; + OptionButton *device_id; OptionButton *device_index; Label *device_index_label; MenuButton *popup_copy_to_feature; @@ -170,6 +170,10 @@ protected: void _notification(int p_what); static void _bind_methods(); + int _get_current_device(); + void _set_current_device(int i_device); + String _get_device_string(int i_device); + public: void add_translation(const String &p_translation); static ProjectSettingsEditor *get_singleton() { return singleton; } diff --git a/editor/property_editor.cpp b/editor/property_editor.cpp index 043add046e..4bd70d0c29 100644 --- a/editor/property_editor.cpp +++ b/editor/property_editor.cpp @@ -285,6 +285,11 @@ void CustomPropertyEditor::_menu_option(int p_which) { } Object *obj = ClassDB::instance(intype); + + if (!obj) { + obj = EditorNode::get_editor_data().instance_custom_type(intype, "Resource"); + } + ERR_BREAK(!obj); Resource *res = Object::cast_to<Resource>(obj); ERR_BREAK(!res); @@ -877,6 +882,12 @@ bool CustomPropertyEditor::edit(Object *p_owner, const String &p_name, Variant:: } else if (hint_text != "") { int idx = 0; + Vector<EditorData::CustomType> custom_resources; + + if (EditorNode::get_editor_data().get_custom_types().has("Resource")) { + custom_resources = EditorNode::get_editor_data().get_custom_types()["Resource"]; + } + for (int i = 0; i < hint_text.get_slice_count(","); i++) { String base = hint_text.get_slice(",", i); @@ -885,6 +896,11 @@ bool CustomPropertyEditor::edit(Object *p_owner, const String &p_name, Variant:: valid_inheritors.insert(base); List<StringName> inheritors; ClassDB::get_inheriters_from_class(base.strip_edges(), &inheritors); + + for (int i = 0; i < custom_resources.size(); i++) { + inheritors.push_back(custom_resources[i].name); + } + List<StringName>::Element *E = inheritors.front(); while (E) { valid_inheritors.insert(E->get()); @@ -893,14 +909,34 @@ bool CustomPropertyEditor::edit(Object *p_owner, const String &p_name, Variant:: for (Set<String>::Element *E = valid_inheritors.front(); E; E = E->next()) { String t = E->get(); - if (!ClassDB::can_instance(t)) + + bool is_custom_resource = false; + Ref<Texture> icon; + if (!custom_resources.empty()) { + for (int i = 0; i < custom_resources.size(); i++) { + if (custom_resources[i].name == t) { + is_custom_resource = true; + if (custom_resources[i].icon.is_valid()) + icon = custom_resources[i].icon; + break; + } + } + } + + if (!is_custom_resource && !ClassDB::can_instance(t)) continue; + inheritors_array.push_back(t); int id = TYPE_BASE_ID + idx; - if (has_icon(t, "EditorIcons")) { - menu->add_icon_item(get_icon(t, "EditorIcons"), vformat(TTR("New %s"), t), id); + if (!icon.is_valid() && has_icon(t, "EditorIcons")) { + icon = get_icon(t, "EditorIcons"); + } + + if (icon.is_valid()) { + + menu->add_icon_item(icon, vformat(TTR("New %s"), t), id); } else { menu->add_item(vformat(TTR("New %s"), t), id); @@ -1094,6 +1130,10 @@ void CustomPropertyEditor::_type_create_selected(int p_idx) { Object *obj = ClassDB::instance(intype); + if (!obj) { + obj = EditorNode::get_editor_data().instance_custom_type(intype, "Resource"); + } + ERR_FAIL_COND(!obj); Resource *res = Object::cast_to<Resource>(obj); @@ -1291,6 +1331,11 @@ void CustomPropertyEditor::_action_pressed(int p_which) { if (hint == PROPERTY_HINT_RESOURCE_TYPE) { Object *obj = ClassDB::instance(intype); + + if (!obj) { + obj = EditorNode::get_editor_data().instance_custom_type(intype, "Resource"); + } + ERR_BREAK(!obj); Resource *res = Object::cast_to<Resource>(obj); ERR_BREAK(!res); @@ -3939,7 +3984,7 @@ void PropertyEditor::_edit_button(Object *p_item, int p_column, int p_button) { if (_might_be_in_instance() && _get_instanced_node_original_property(prop, vorig)) { - _edit_set(prop, vorig); + _edit_set(prop, vorig.duplicate(true)); // Set, making sure to duplicate arrays properly return; } diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 1644ae5b55..b13b238fd7 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -916,9 +916,6 @@ void SceneTreeDock::perform_node_renames(Node *p_base, List<Pair<NodePath, NodeP if (!r_rem_anims) r_rem_anims = &rem_anims; - if (!bool(EDITOR_DEF("editors/animation/autorename_animation_tracks", true))) - return; - if (!p_base) { p_base = edited_scene; @@ -927,7 +924,54 @@ void SceneTreeDock::perform_node_renames(Node *p_base, List<Pair<NodePath, NodeP if (!p_base) return; - if (Object::cast_to<AnimationPlayer>(p_base)) { + // Renaming node paths used in script instances + if (p_base->get_script_instance()) { + + ScriptInstance *si = p_base->get_script_instance(); + + if (si) { + + List<PropertyInfo> properties; + si->get_property_list(&properties); + + for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { + + String propertyname = E->get().name; + Variant p = p_base->get(propertyname); + if (p.get_type() == Variant::NODE_PATH) { + + // Goes through all paths to check if its matching + for (List<Pair<NodePath, NodePath> >::Element *E = p_renames->front(); E; E = E->next()) { + + NodePath root_path = p_base->get_path(); + + NodePath rel_path_old = root_path.rel_path_to(E->get().first); + + NodePath rel_path_new = E->get().second; + + // if not empty, get new relative path + if (E->get().second != NodePath()) { + rel_path_new = root_path.rel_path_to(E->get().second); + } + + // if old path detected, then it needs to be replaced with the new one + if (p == rel_path_old) { + + editor_data->get_undo_redo().add_do_property(p_base, propertyname, rel_path_new); + editor_data->get_undo_redo().add_undo_property(p_base, propertyname, rel_path_old); + + p_base->set(propertyname, rel_path_new); + break; + } + } + } + } + } + } + + bool autorename_animation_tracks = bool(EDITOR_DEF("editors/animation/autorename_animation_tracks", true)); + + if (autorename_animation_tracks && Object::cast_to<AnimationPlayer>(p_base)) { AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(p_base); List<StringName> anims; @@ -1158,7 +1202,30 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V editor_data->get_undo_redo().add_do_method(new_parent, "move_child", node, p_position_in_parent + inc); ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger(); + String old_name = former_names[ni]; String new_name = new_parent->validate_child_name(node); + + // name was modified, fix the path renames + if (old_name.casecmp_to(new_name) != 0) { + + // Fix the to name to have the new name + NodePath old_new_name = path_renames[ni].second; + NodePath new_path; + + Vector<StringName> unfixed_new_names = old_new_name.get_names(); + Vector<StringName> fixed_new_names; + + // Get last name and replace with fixed new name + for (int a = 0; a < (unfixed_new_names.size() - 1); a++) { + fixed_new_names.push_back(unfixed_new_names[a]); + } + fixed_new_names.push_back(new_name); + + NodePath fixed_node_path = NodePath(fixed_new_names, true); + + path_renames[ni].second = fixed_node_path; + } + editor_data->get_undo_redo().add_do_method(sed, "live_debug_reparent_node", edited_scene->get_path_to(node), edited_scene->get_path_to(new_parent), new_name, -1); editor_data->get_undo_redo().add_undo_method(sed, "live_debug_reparent_node", NodePath(String(edited_scene->get_path_to(new_parent)) + "/" + new_name), edited_scene->get_path_to(node->get_parent()), node->get_name(), node->get_index()); diff --git a/editor/settings_config_dialog.cpp b/editor/settings_config_dialog.cpp index d75ef5df8a..36d7a83930 100644 --- a/editor/settings_config_dialog.cpp +++ b/editor/settings_config_dialog.cpp @@ -199,6 +199,14 @@ void EditorSettingsDialog::_update_icons() { void EditorSettingsDialog::_update_shortcuts() { + Map<String, bool> collapsed; + + if (shortcuts->get_root() && shortcuts->get_root()->get_children()) { + for (TreeItem *item = shortcuts->get_root()->get_children(); item; item = item->get_next()) { + collapsed[item->get_text(0)] = item->is_collapsed(); + } + } + shortcuts->clear(); List<String> slist; @@ -223,7 +231,13 @@ void EditorSettingsDialog::_update_shortcuts() { section = sections[section_name]; } else { section = shortcuts->create_item(root); - section->set_text(0, section_name.capitalize()); + + String item_name = section_name.capitalize(); + section->set_text(0, item_name); + + if (collapsed.has(item_name)) { + section->set_collapsed(collapsed[item_name]); + } sections[section_name] = section; section->set_custom_bg_color(0, get_color("prop_subsection", "Editor")); |