diff options
Diffstat (limited to 'editor')
42 files changed, 1200 insertions, 226 deletions
diff --git a/editor/SCsub b/editor/SCsub index 13ae85bbf0..651dd5fffd 100644 --- a/editor/SCsub +++ b/editor/SCsub @@ -6,6 +6,7 @@ env.editor_sources = [] import os import os.path +import glob from platform_methods import run_in_subprocess import editor_builders @@ -40,20 +41,21 @@ if env["tools"]: f.write(reg_exporters_inc) f.write(reg_exporters) - # API documentation + # Core API documentation. docs = [] - doc_dirs = ["doc/classes"] + docs += Glob("#doc/classes/*.xml") - for p in env.doc_class_path.values(): - if p not in doc_dirs: - doc_dirs.append(p) + # Module API documentation. + module_dirs = [] + for d in env.doc_class_path.values(): + if d not in module_dirs: + module_dirs.append(d) - for d in doc_dirs: - try: - for f in os.listdir(os.path.join(env.Dir("#").abspath, d)): - docs.append("#" + os.path.join(d, f)) - except OSError: - pass + for d in module_dirs: + if not os.path.isabs(d): + docs += Glob("#" + d + "/*.xml") # Built-in. + else: + docs += Glob(d + "/*.xml") # Custom. _make_doc_data_class_path(os.path.join(env.Dir("#").abspath, "editor")) @@ -61,8 +63,6 @@ if env["tools"]: env.Depends("#editor/doc_data_compressed.gen.h", docs) env.CommandNoCache("#editor/doc_data_compressed.gen.h", docs, run_in_subprocess(editor_builders.make_doc_header)) - import glob - path = env.Dir(".").abspath # Editor translations diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 2a8e0d856e..8fd1f5951e 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -3199,7 +3199,7 @@ void AnimationTrackEditor::update_keying() { } keying = keying_enabled; - //_update_menu(); + emit_signal("keying_changed"); } @@ -4882,12 +4882,12 @@ void AnimationTrackEditor::_box_selection_draw() { void AnimationTrackEditor::_scroll_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid() && mb->is_pressed() && mb->get_command() && mb->get_button_index() == BUTTON_WHEEL_DOWN) { + if (mb.is_valid() && mb->is_pressed() && mb->get_command() && mb->get_button_index() == BUTTON_WHEEL_UP) { timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() * 1.05); scroll->accept_event(); } - if (mb.is_valid() && mb->is_pressed() && mb->get_command() && mb->get_button_index() == BUTTON_WHEEL_UP) { + if (mb.is_valid() && mb->is_pressed() && mb->get_command() && mb->get_button_index() == BUTTON_WHEEL_DOWN) { timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() / 1.05); scroll->accept_event(); } diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 3ea970a0f0..9888013ce6 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -657,7 +657,7 @@ FindReplaceBar::FindReplaceBar() { // be handled too late if they weren't handled here. void CodeTextEditor::_input(const Ref<InputEvent> &event) { const Ref<InputEventKey> key_event = event; - if (!key_event.is_valid() || !key_event->is_pressed()) { + if (!key_event.is_valid() || !key_event->is_pressed() || !text_editor->has_focus()) { return; } diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index 62b5911ac1..6507956d07 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -984,7 +984,7 @@ void ConnectionsDock::update_tree() { while (F && descr == String()) { for (int i = 0; i < F->get().signals.size(); i++) { if (F->get().signals[i].name == signal_name.operator String()) { - descr = DTR(F->get().signals[i].description.strip_edges()); + descr = DTR(F->get().signals[i].description); break; } } diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp index 75fff03297..e4a853dbd7 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -53,13 +53,15 @@ void CreateDialog::popup_create(bool p_dont_clear, bool p_replace_mode, const St if (f) { TreeItem *root = recent->create_item(); + String icon_fallback = search_options->has_theme_icon(base_type, "EditorIcons") ? base_type : "Object"; + while (!f->eof_reached()) { String l = f->get_line().strip_edges(); String name = l.split(" ")[0]; if ((ClassDB::class_exists(name) || ScriptServer::is_global_class(name)) && !_is_class_disabled_by_feature_profile(name)) { TreeItem *ti = recent->create_item(root); ti->set_text(0, l); - ti->set_icon(0, EditorNode::get_singleton()->get_class_icon(l, base_type)); + ti->set_icon(0, EditorNode::get_singleton()->get_class_icon(name, icon_fallback)); } } @@ -248,7 +250,8 @@ void CreateDialog::add_type(const String &p_type, HashMap<String, TreeItem *> &p const String &description = DTR(EditorHelp::get_doc_data()->class_list[p_type].brief_description); item->set_tooltip(0, description); - item->set_icon(0, EditorNode::get_singleton()->get_class_icon(p_type, base_type)); + String icon_fallback = search_options->has_theme_icon(base_type, "EditorIcons") ? base_type : "Object"; + item->set_icon(0, EditorNode::get_singleton()->get_class_icon(p_type, icon_fallback)); p_types[p_type] = item; } @@ -305,9 +308,8 @@ void CreateDialog::_update_search() { EditorData &ed = EditorNode::get_editor_data(); root->set_text(0, base_type); - if (search_options->has_theme_icon(base_type, "EditorIcons")) { - root->set_icon(0, search_options->get_theme_icon(base_type, "EditorIcons")); - } + String base_icon = search_options->has_theme_icon(base_type, "EditorIcons") ? base_type : "Object"; + root->set_icon(0, search_options->get_theme_icon(base_icon, "EditorIcons")); TreeItem *to_select = search_box->get_text() == base_type ? root : nullptr; @@ -393,9 +395,7 @@ void CreateDialog::_update_search() { TreeItem *item = search_options->create_item(ti); item->set_metadata(0, type); item->set_text(0, ct[i].name); - if (ct[i].icon.is_valid()) { - item->set_icon(0, ct[i].icon); - } + item->set_icon(0, ct[i].icon.is_valid() ? ct[i].icon : search_options->get_theme_icon(base_icon, "EditorIcons")); if (!to_select || ct[i].name == search_box->get_text()) { to_select = item; @@ -598,16 +598,21 @@ void CreateDialog::_save_favorite_list() { void CreateDialog::_update_favorite_list() { favorites->clear(); + TreeItem *root = favorites->create_item(); + + String icon_fallback = search_options->has_theme_icon(base_type, "EditorIcons") ? base_type : "Object"; + for (int i = 0; i < favorite_list.size(); i++) { String l = favorite_list[i]; String name = l.split(" ")[0]; if (!((ClassDB::class_exists(name) || ScriptServer::is_global_class(name)) && !_is_class_disabled_by_feature_profile(name))) { continue; } + TreeItem *ti = favorites->create_item(root); ti->set_text(0, l); - ti->set_icon(0, EditorNode::get_singleton()->get_class_icon(l, base_type)); + ti->set_icon(0, EditorNode::get_singleton()->get_class_icon(name, icon_fallback)); } emit_signal("favorites_updated"); } diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp index 200ae7045f..da0ff9f18f 100644 --- a/editor/editor_autoload_settings.cpp +++ b/editor/editor_autoload_settings.cpp @@ -674,18 +674,18 @@ bool EditorAutoloadSettings::autoload_add(const String &p_name, const String &p_ String error; if (!_autoload_name_is_valid(name, &error)) { - EditorNode::get_singleton()->show_warning(error); + EditorNode::get_singleton()->show_warning(TTR("Can't add autoload:") + "\n" + error); return false; } const String &path = p_path; if (!FileAccess::exists(path)) { - EditorNode::get_singleton()->show_warning(TTR("Invalid path.") + "\n" + TTR("File does not exist.")); + EditorNode::get_singleton()->show_warning(TTR("Can't add autoload:") + "\n" + TTR(vformat("%s is an invalid path. File does not exist.", path))); return false; } if (!path.begins_with("res://")) { - EditorNode::get_singleton()->show_warning(TTR("Invalid path.") + "\n" + TTR("Not in resource path.")); + EditorNode::get_singleton()->show_warning(TTR("Can't add autoload:") + "\n" + TTR(vformat("%s is an invalid path. Not in resource path (res://).", path))); return false; } @@ -912,4 +912,4 @@ void EditorAutoloadSettings::_set_autoload_add_path(const String &p_text) { void EditorAutoloadSettings::_browse_autoload_add_path() { file_dialog->popup_centered_ratio(); -}
\ No newline at end of file +} diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp index d3749477cc..2a410c03e7 100644 --- a/editor/editor_feature_profile.cpp +++ b/editor/editor_feature_profile.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "editor_feature_profile.h" + #include "core/io/json.h" #include "core/os/dir_access.h" #include "editor/editor_settings.h" @@ -353,7 +354,7 @@ void EditorFeatureProfileManager::_update_profile_list(const String &p_select_pr } if (name == current_profile) { - name += " (current)"; + name += " " + TTR("(current)"); } profile_list->add_item(name); int index = profile_list->get_item_count() - 1; @@ -363,12 +364,15 @@ void EditorFeatureProfileManager::_update_profile_list(const String &p_select_pr } } + class_list_vbc->set_visible(selected_profile != String()); + property_list_vbc->set_visible(selected_profile != String()); + no_profile_selected_help->set_visible(selected_profile == String()); profile_actions[PROFILE_CLEAR]->set_disabled(current_profile == String()); profile_actions[PROFILE_ERASE]->set_disabled(selected_profile == String()); profile_actions[PROFILE_EXPORT]->set_disabled(selected_profile == String()); profile_actions[PROFILE_SET]->set_disabled(selected_profile == String()); - current_profile_name->set_text(current_profile); + current_profile_name->set_text(current_profile != String() ? current_profile : TTR("(none)")); _update_selected_profile(); } @@ -451,6 +455,10 @@ void EditorFeatureProfileManager::_create_new_profile() { new_profile->save_to_file(file); _update_profile_list(name); + // The newly created profile is the first one, make it the current profile automatically. + if (profile_list->get_item_count() == 1) { + _profile_action(PROFILE_SET); + } } void EditorFeatureProfileManager::_profile_selected(int p_what) { @@ -730,6 +738,10 @@ void EditorFeatureProfileManager::_import_profiles(const Vector<String> &p_paths } _update_profile_list(); + // The newly imported profile is the first one, make it the current profile automatically. + if (profile_list->get_item_count() == 1) { + _profile_action(PROFILE_SET); + } } void EditorFeatureProfileManager::_export_profile(const String &p_path) { @@ -779,6 +791,7 @@ EditorFeatureProfileManager::EditorFeatureProfileManager() { HBoxContainer *name_hbc = memnew(HBoxContainer); current_profile_name = memnew(LineEdit); name_hbc->add_child(current_profile_name); + current_profile_name->set_text(TTR("(none)")); current_profile_name->set_editable(false); current_profile_name->set_h_size_flags(Control::SIZE_EXPAND_FILL); profile_actions[PROFILE_CLEAR] = memnew(Button(TTR("Unset"))); @@ -827,7 +840,7 @@ EditorFeatureProfileManager::EditorFeatureProfileManager() { h_split->set_v_size_flags(Control::SIZE_EXPAND_FILL); main_vbc->add_child(h_split); - VBoxContainer *class_list_vbc = memnew(VBoxContainer); + class_list_vbc = memnew(VBoxContainer); h_split->add_child(class_list_vbc); class_list_vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -837,17 +850,30 @@ EditorFeatureProfileManager::EditorFeatureProfileManager() { class_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true); class_list->connect("cell_selected", callable_mp(this, &EditorFeatureProfileManager::_class_list_item_selected)); class_list->connect("item_edited", callable_mp(this, &EditorFeatureProfileManager::_class_list_item_edited), varray(), CONNECT_DEFERRED); + // It will be displayed once the user creates or chooses a profile. + class_list_vbc->hide(); - VBoxContainer *property_list_vbc = memnew(VBoxContainer); + property_list_vbc = memnew(VBoxContainer); h_split->add_child(property_list_vbc); property_list_vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); property_list = memnew(Tree); - property_list_vbc->add_margin_child(TTR("Class Options"), property_list, true); + property_list_vbc->add_margin_child(TTR("Class Options:"), property_list, true); property_list->set_hide_root(true); property_list->set_hide_folding(true); property_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true); property_list->connect("item_edited", callable_mp(this, &EditorFeatureProfileManager::_property_item_edited), varray(), CONNECT_DEFERRED); + // It will be displayed once the user creates or chooses a profile. + property_list_vbc->hide(); + + no_profile_selected_help = memnew(Label(TTR("Create or import a profile to edit available classes and properties."))); + // Add some spacing above the help label. + Ref<StyleBoxEmpty> sb = memnew(StyleBoxEmpty); + sb->set_default_margin(MARGIN_TOP, 20 * EDSCALE); + no_profile_selected_help->add_theme_style_override("normal", sb); + no_profile_selected_help->set_align(Label::ALIGN_CENTER); + no_profile_selected_help->set_v_size_flags(Control::SIZE_EXPAND_FILL); + h_split->add_child(no_profile_selected_help); new_profile_dialog = memnew(ConfirmationDialog); new_profile_dialog->set_title(TTR("New profile name:")); diff --git a/editor/editor_feature_profile.h b/editor/editor_feature_profile.h index 4036ec7ec6..38413e35a2 100644 --- a/editor/editor_feature_profile.h +++ b/editor/editor_feature_profile.h @@ -120,8 +120,11 @@ class EditorFeatureProfileManager : public AcceptDialog { HSplitContainer *h_split; + VBoxContainer *class_list_vbc; Tree *class_list; + VBoxContainer *property_list_vbc; Tree *property_list; + Label *no_profile_selected_help; EditorFileDialog *import_profiles; EditorFileDialog *export_profile; diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 1b423f69b7..a8ded44323 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -1583,9 +1583,9 @@ void EditorInspector::update_tree() { DocData *dd = EditorHelp::get_doc_data(); Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(type2); if (E) { - descr = E->get().brief_description; + descr = DTR(E->get().brief_description); } - class_descr_cache[type2] = DTR(descr); + class_descr_cache[type2] = descr; } category->set_tooltip(p.name + "::" + (class_descr_cache[type2] == "" ? "" : class_descr_cache[type2])); @@ -1755,7 +1755,7 @@ void EditorInspector::update_tree() { while (F && descr == String()) { for (int i = 0; i < F->get().properties.size(); i++) { if (F->get().properties[i].name == propname.operator String()) { - descr = DTR(F->get().properties[i].description.strip_edges()); + descr = DTR(F->get().properties[i].description); break; } } @@ -1765,7 +1765,7 @@ void EditorInspector::update_tree() { // Likely a theme property. for (int i = 0; i < F->get().theme_properties.size(); i++) { if (F->get().theme_properties[i].name == slices[1]) { - descr = DTR(F->get().theme_properties[i].description.strip_edges()); + descr = DTR(F->get().theme_properties[i].description); break; } } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 8b7014fabe..14a03c5377 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -4782,7 +4782,7 @@ void EditorNode::set_distraction_free_mode(bool p_enter) { } } -bool EditorNode::get_distraction_free_mode() const { +bool EditorNode::is_distraction_free_mode_enabled() const { return distraction_free->is_pressed(); } @@ -6568,6 +6568,7 @@ EditorNode::EditorNode() { gui_base->add_child(load_error_dialog); execute_outputs = memnew(RichTextLabel); + execute_outputs->set_selection_enabled(true); execute_output_dialog = memnew(AcceptDialog); execute_output_dialog->add_child(execute_outputs); execute_output_dialog->set_title(""); diff --git a/editor/editor_node.h b/editor/editor_node.h index dfe3d91c07..7c9cf44d6c 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -693,7 +693,7 @@ public: bool get_docks_visible() const; void set_distraction_free_mode(bool p_enter); - bool get_distraction_free_mode() const; + bool is_distraction_free_mode_enabled() const; void add_control_to_dock(DockSlot p_slot, Control *p_control); void remove_control_from_dock(Control *p_control); diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp index 2365090f03..6d93e92555 100644 --- a/editor/editor_plugin.cpp +++ b/editor/editor_plugin.cpp @@ -270,6 +270,10 @@ void EditorInterface::set_distraction_free_mode(bool p_enter) { EditorNode::get_singleton()->set_distraction_free_mode(p_enter); } +bool EditorInterface::is_distraction_free_mode_enabled() const { + return EditorNode::get_singleton()->is_distraction_free_mode_enabled(); +} + EditorInterface *EditorInterface::singleton = nullptr; void EditorInterface::_bind_methods() { @@ -302,6 +306,9 @@ void EditorInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("set_main_screen_editor", "name"), &EditorInterface::set_main_screen_editor); ClassDB::bind_method(D_METHOD("set_distraction_free_mode", "enter"), &EditorInterface::set_distraction_free_mode); + ClassDB::bind_method(D_METHOD("is_distraction_free_mode_enabled"), &EditorInterface::is_distraction_free_mode_enabled); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "distraction_free_mode"), "set_distraction_free_mode", "is_distraction_free_mode_enabled"); } EditorInterface::EditorInterface() { diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h index 2792c8bf19..aac36bfdfd 100644 --- a/editor/editor_plugin.h +++ b/editor/editor_plugin.h @@ -105,6 +105,7 @@ public: void set_main_screen_editor(const String &p_name); void set_distraction_free_mode(bool p_enter); + bool is_distraction_free_mode_enabled() const; EditorInterface(); }; diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index a16605ab44..5f293f1fb3 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -503,17 +503,38 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("editors/grid_map/pick_distance", 5000.0); // 3D - _initial_set("editors/3d/primary_grid_color", Color(0.56, 0.56, 0.56)); - hints["editors/3d/primary_grid_color"] = PropertyInfo(Variant::COLOR, "editors/3d/primary_grid_color", PROPERTY_HINT_COLOR_NO_ALPHA, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); + _initial_set("editors/3d/primary_grid_color", Color(0.56, 0.56, 0.56, 0.5)); + hints["editors/3d/primary_grid_color"] = PropertyInfo(Variant::COLOR, "editors/3d/primary_grid_color", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); - _initial_set("editors/3d/secondary_grid_color", Color(0.38, 0.38, 0.38)); - hints["editors/3d/secondary_grid_color"] = PropertyInfo(Variant::COLOR, "editors/3d/secondary_grid_color", PROPERTY_HINT_COLOR_NO_ALPHA, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); - - _initial_set("editors/3d/grid_size", 50); - hints["editors/3d/grid_size"] = PropertyInfo(Variant::INT, "editors/3d/grid_size", PROPERTY_HINT_RANGE, "1,500,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); + _initial_set("editors/3d/secondary_grid_color", Color(0.38, 0.38, 0.38, 0.5)); + hints["editors/3d/secondary_grid_color"] = PropertyInfo(Variant::COLOR, "editors/3d/secondary_grid_color", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); + // If a line is a multiple of this, it uses the primary grid color. _initial_set("editors/3d/primary_grid_steps", 10); - hints["editors/3d/primary_grid_steps"] = PropertyInfo(Variant::INT, "editors/3d/primary_grid_steps", PROPERTY_HINT_RANGE, "1,100,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); + hints["editors/3d/primary_grid_steps"] = PropertyInfo(Variant::INT, "editors/3d/primary_grid_steps", PROPERTY_HINT_RANGE, "1,100,1", PROPERTY_USAGE_DEFAULT); + + // At 1000, the grid mostly looks like it has no edge. + _initial_set("editors/3d/grid_size", 200); + hints["editors/3d/grid_size"] = PropertyInfo(Variant::INT, "editors/3d/grid_size", PROPERTY_HINT_RANGE, "1,2000,1", PROPERTY_USAGE_DEFAULT); + + // Default largest grid size is 100m, 10^2 (primary grid lines are 1km apart when primary_grid_steps is 10). + _initial_set("editors/3d/grid_division_level_max", 2); + // Higher values produce graphical artifacts when far away unless View Z-Far + // is increased significantly more than it really should need to be. + hints["editors/3d/grid_division_level_max"] = PropertyInfo(Variant::INT, "editors/3d/grid_division_level_max", PROPERTY_HINT_RANGE, "-1,3,1", PROPERTY_USAGE_DEFAULT); + + // Default smallest grid size is 1cm, 10^-2. + _initial_set("editors/3d/grid_division_level_min", -2); + // Lower values produce graphical artifacts regardless of view clipping planes, so limit to -2 as a lower bound. + hints["editors/3d/grid_division_level_min"] = PropertyInfo(Variant::INT, "editors/3d/grid_division_level_min", PROPERTY_HINT_RANGE, "-2,2,1", PROPERTY_USAGE_DEFAULT); + + // -0.2 seems like a sensible default. -1.0 gives Blender-like behavior, 0.5 gives huge grids. + _initial_set("editors/3d/grid_division_level_bias", -0.2); + hints["editors/3d/grid_division_level_bias"] = PropertyInfo(Variant::FLOAT, "editors/3d/grid_division_level_bias", PROPERTY_HINT_RANGE, "-1.0,0.5,0.1", PROPERTY_USAGE_DEFAULT); + + _initial_set("editors/3d/grid_xz_plane", true); + _initial_set("editors/3d/grid_xy_plane", false); + _initial_set("editors/3d/grid_yz_plane", false); _initial_set("editors/3d/default_fov", 70.0); _initial_set("editors/3d/default_z_near", 0.05); diff --git a/editor/icons/SCsub b/editor/icons/SCsub index f0d51999f0..e143276259 100644 --- a/editor/icons/SCsub +++ b/editor/icons/SCsub @@ -2,6 +2,8 @@ Import("env") +import os + from platform_methods import run_in_subprocess import editor_icons_builders @@ -15,7 +17,10 @@ env["BUILDERS"]["MakeEditorIconsBuilder"] = make_editor_icons_builder icon_sources = Glob("*.svg") # Module icons -for module_icons in env.module_icons_paths: - icon_sources += Glob("#" + module_icons + "/*.svg") +for path in env.module_icons_paths: + if not os.path.isabs(path): + icon_sources += Glob("#" + path + "/*.svg") # Built-in. + else: + icon_sources += Glob(path + "/*.svg") # Custom. env.Alias("editor_icons", [env.MakeEditorIconsBuilder("#editor/editor_icons.gen.h", icon_sources)]) diff --git a/editor/import/resource_importer_layered_texture.cpp b/editor/import/resource_importer_layered_texture.cpp index 1f39a12c25..b57ea3745d 100644 --- a/editor/import/resource_importer_layered_texture.cpp +++ b/editor/import/resource_importer_layered_texture.cpp @@ -134,7 +134,7 @@ String ResourceImporterLayeredTexture::get_preset_name(int p_idx) const { } void ResourceImporterLayeredTexture::get_import_options(List<ImportOption> *r_options, int p_preset) const { - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "Lossless,Lossy,Video RAM,Uncompressed,Basis Universal", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 1)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "Lossless (PNG),Lossy (WebP),Video RAM (S3TC/ETC/BPTC),Uncompressed,Basis Universal", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 1)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "compress/lossy_quality", PROPERTY_HINT_RANGE, "0,1,0.01"), 0.7)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/hdr_compression", PROPERTY_HINT_ENUM, "Disabled,Opaque Only,Always"), 1)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/bptc_ldr", PROPERTY_HINT_ENUM, "Disabled,Enabled,RGBA Only"), 0)); diff --git a/editor/import/resource_importer_texture_atlas.cpp b/editor/import/resource_importer_texture_atlas.cpp index 0818655c4c..2423553d22 100644 --- a/editor/import/resource_importer_texture_atlas.cpp +++ b/editor/import/resource_importer_texture_atlas.cpp @@ -33,6 +33,7 @@ #include "atlas_import_failed.xpm" #include "core/io/image_loader.h" #include "core/io/resource_saver.h" +#include "core/math/geometry_2d.h" #include "core/os/file_access.h" #include "editor/editor_atlas_packer.h" #include "scene/resources/mesh.h" @@ -129,7 +130,8 @@ static void _plot_triangle(Vector2 *vertices, const Vector2 &p_offset, bool p_tr double dx_low = double(x[2] - x[1]) / (y[2] - y[1] + 1); double xf = x[0]; double xt = x[0] + dx_upper; // if y[0] == y[1], special case - for (int yi = y[0]; yi <= (y[2] > height - 1 ? height - 1 : y[2]); yi++) { + int max_y = MIN(y[2], height - p_offset.y - 1); + for (int yi = y[0]; yi <= max_y; yi++) { if (yi >= 0) { for (int xi = (xf > 0 ? int(xf) : 0); xi <= (xt < width ? xt : width - 1); xi++) { int px = xi, py = yi; @@ -247,7 +249,7 @@ Error ResourceImporterTextureAtlas::import_group_file(const String &p_group_file chart.vertices = polygons[j]; chart.can_transpose = true; - Vector<int> poly = Geometry::triangulate_polygon(polygons[j]); + Vector<int> poly = Geometry2D::triangulate_polygon(polygons[j]); for (int i = 0; i < poly.size(); i += 3) { EditorAtlasPacker::Chart::Face f; f.vertex[0] = poly[i + 0]; diff --git a/editor/node_3d_editor_gizmos.cpp b/editor/node_3d_editor_gizmos.cpp index 0eb4e6036a..2ddcf3d877 100644 --- a/editor/node_3d_editor_gizmos.cpp +++ b/editor/node_3d_editor_gizmos.cpp @@ -30,7 +30,8 @@ #include "node_3d_editor_gizmos.h" -#include "core/math/geometry.h" +#include "core/math/geometry_2d.h" +#include "core/math/geometry_3d.h" #include "core/math/quick_hull.h" #include "scene/3d/audio_stream_player_3d.h" #include "scene/3d/baked_lightmap.h" @@ -482,7 +483,7 @@ bool EditorNode3DGizmo::intersect_frustum(const Camera3D *p_camera, const Vector transformed_frustum.push_back(it.xform(p_frustum[i])); } - Vector<Vector3> convex_points = Geometry::compute_convex_mesh_points(p_frustum.ptr(), p_frustum.size()); + Vector<Vector3> convex_points = Geometry3D::compute_convex_mesh_points(p_frustum.ptr(), p_frustum.size()); if (collision_mesh->inside_convex_shape(transformed_frustum.ptr(), transformed_frustum.size(), convex_points.ptr(), convex_points.size(), mesh_scale)) { return true; } @@ -616,7 +617,7 @@ bool EditorNode3DGizmo::intersect_ray(Camera3D *p_camera, const Point2 &p_point, s[0] = p_camera->unproject_position(a); s[1] = p_camera->unproject_position(b); - Vector2 p = Geometry::get_closest_point_to_segment_2d(p_point, s); + Vector2 p = Geometry2D::get_closest_point_to_segment(p_point, s); float pd = p.distance_to(p_point); @@ -831,7 +832,7 @@ static float _find_closest_angle_to_half_pi_arc(const Vector3 &p_from, const Vec Vector3 n = Vector3(Math::cos(an), 0, -Math::sin(an)) * p_arc_radius; Vector3 ra, rb; - Geometry::get_closest_points_between_segments(p, n, p_from, p_to, ra, rb); + Geometry3D::get_closest_points_between_segments(p, n, p_from, p_to, ra, rb); float d = ra.distance_to(rb); if (d < min_d) { @@ -857,7 +858,7 @@ void Light3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camer if (p_idx == 0) { if (Object::cast_to<SpotLight3D>(light)) { Vector3 ra, rb; - Geometry::get_closest_points_between_segments(Vector3(), Vector3(0, 0, -4096), s[0], s[1], ra, rb); + Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(0, 0, -4096), s[0], s[1], ra, rb); float d = -ra.z; if (Node3DEditor::get_singleton()->is_snap_enabled()) { @@ -1100,7 +1101,7 @@ void AudioStreamPlayer3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int Vector3 to(Math::sin(an), 0, -Math::cos(an)); Vector3 r1, r2; - Geometry::get_closest_points_between_segments(from, to, ray_from, ray_to, r1, r2); + Geometry3D::get_closest_points_between_segments(from, to, ray_from, ray_to, r1, r2); float d = r1.distance_to(r2); if (d < closest_dist) { closest_dist = d; @@ -1238,7 +1239,7 @@ void Camera3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Came camera->set("fov", CLAMP(a * 2.0, 1, 179)); } else { Vector3 ra, rb; - Geometry::get_closest_points_between_segments(Vector3(0, 0, -1), Vector3(4096, 0, -1), s[0], s[1], ra, rb); + Geometry3D::get_closest_points_between_segments(Vector3(0, 0, -1), Vector3(4096, 0, -1), s[0], s[1], ra, rb); float d = ra.x * 2.0; if (Node3DEditor::get_singleton()->is_snap_enabled()) { d = Math::stepify(d, Node3DEditor::get_singleton()->get_translate_snap()); @@ -2169,7 +2170,7 @@ void VisibilityNotifier3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int if (move) { Vector3 ra, rb; - Geometry::get_closest_points_between_segments(ofs - axis * 4096, ofs + axis * 4096, sg[0], sg[1], ra, rb); + Geometry3D::get_closest_points_between_segments(ofs - axis * 4096, ofs + axis * 4096, sg[0], sg[1], ra, rb); float d = ra[p_idx]; if (Node3DEditor::get_singleton()->is_snap_enabled()) { @@ -2181,7 +2182,7 @@ void VisibilityNotifier3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int } else { Vector3 ra, rb; - Geometry::get_closest_points_between_segments(ofs, ofs + axis * 4096, sg[0], sg[1], ra, rb); + Geometry3D::get_closest_points_between_segments(ofs, ofs + axis * 4096, sg[0], sg[1], ra, rb); float d = ra[p_idx] - ofs[p_idx]; if (Node3DEditor::get_singleton()->is_snap_enabled()) { @@ -2360,7 +2361,7 @@ void GPUParticles3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx if (move) { Vector3 ra, rb; - Geometry::get_closest_points_between_segments(ofs - axis * 4096, ofs + axis * 4096, sg[0], sg[1], ra, rb); + Geometry3D::get_closest_points_between_segments(ofs - axis * 4096, ofs + axis * 4096, sg[0], sg[1], ra, rb); float d = ra[p_idx]; if (Node3DEditor::get_singleton()->is_snap_enabled()) { @@ -2372,7 +2373,7 @@ void GPUParticles3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx } else { Vector3 ra, rb; - Geometry::get_closest_points_between_segments(ofs, ofs + axis * 4096, sg[0], sg[1], ra, rb); + Geometry3D::get_closest_points_between_segments(ofs, ofs + axis * 4096, sg[0], sg[1], ra, rb); float d = ra[p_idx] - ofs[p_idx]; if (Node3DEditor::get_singleton()->is_snap_enabled()) { @@ -2523,7 +2524,7 @@ void ReflectionProbeGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_id axis[p_idx] = 1.0; Vector3 ra, rb; - Geometry::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb); + Geometry3D::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb); float d = ra[p_idx]; if (Node3DEditor::get_singleton()->is_snap_enabled()) { d = Math::stepify(d, Node3DEditor::get_singleton()->get_translate_snap()); @@ -2550,7 +2551,7 @@ void ReflectionProbeGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_id axis[p_idx] = 1.0; Vector3 ra, rb; - Geometry::get_closest_points_between_segments(origin - axis * 16384, origin + axis * 16384, sg[0], sg[1], ra, rb); + Geometry3D::get_closest_points_between_segments(origin - axis * 16384, origin + axis * 16384, sg[0], sg[1], ra, rb); // Adjust the actual position to account for the gizmo handle position float d = ra[p_idx] + 0.25; if (Node3DEditor::get_singleton()->is_snap_enabled()) { @@ -2701,7 +2702,7 @@ void DecalGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3 axis[p_idx] = 1.0; Vector3 ra, rb; - Geometry::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb); + Geometry3D::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb); float d = ra[p_idx]; if (Node3DEditor::get_singleton()->is_snap_enabled()) { d = Math::stepify(d, Node3DEditor::get_singleton()->get_translate_snap()); @@ -2842,7 +2843,7 @@ void GIProbeGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camer axis[p_idx] = 1.0; Vector3 ra, rb; - Geometry::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb); + Geometry3D::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb); float d = ra[p_idx]; if (Node3DEditor::get_singleton()->is_snap_enabled()) { d = Math::stepify(d, Node3DEditor::get_singleton()->get_translate_snap()); @@ -3354,7 +3355,7 @@ void CollisionShape3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_i if (Object::cast_to<SphereShape3D>(*s)) { Ref<SphereShape3D> ss = s; Vector3 ra, rb; - Geometry::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb); + Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb); float d = ra.x; if (Node3DEditor::get_singleton()->is_snap_enabled()) { d = Math::stepify(d, Node3DEditor::get_singleton()->get_translate_snap()); @@ -3370,7 +3371,7 @@ void CollisionShape3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_i if (Object::cast_to<RayShape3D>(*s)) { Ref<RayShape3D> rs = s; Vector3 ra, rb; - Geometry::get_closest_points_between_segments(Vector3(), Vector3(0, 0, 4096), sg[0], sg[1], ra, rb); + Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(0, 0, 4096), sg[0], sg[1], ra, rb); float d = ra.z; if (Node3DEditor::get_singleton()->is_snap_enabled()) { d = Math::stepify(d, Node3DEditor::get_singleton()->get_translate_snap()); @@ -3388,7 +3389,7 @@ void CollisionShape3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_i axis[p_idx] = 1.0; Ref<BoxShape3D> bs = s; Vector3 ra, rb; - Geometry::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); + Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); float d = ra[p_idx]; if (Node3DEditor::get_singleton()->is_snap_enabled()) { d = Math::stepify(d, Node3DEditor::get_singleton()->get_translate_snap()); @@ -3408,7 +3409,7 @@ void CollisionShape3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_i axis[p_idx == 0 ? 0 : 2] = 1.0; Ref<CapsuleShape3D> cs2 = s; Vector3 ra, rb; - Geometry::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); + Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); float d = axis.dot(ra); if (p_idx == 1) { d -= cs2->get_radius(); @@ -3434,7 +3435,7 @@ void CollisionShape3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_i axis[p_idx == 0 ? 0 : 1] = 1.0; Ref<CylinderShape3D> cs2 = s; Vector3 ra, rb; - Geometry::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); + Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); float d = axis.dot(ra); if (Node3DEditor::get_singleton()->is_snap_enabled()) { d = Math::stepify(d, Node3DEditor::get_singleton()->get_translate_snap()); @@ -3802,7 +3803,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { if (points.size() > 3) { Vector<Vector3> varr = Variant(points); - Geometry::MeshData md; + Geometry3D::MeshData md; Error err = QuickHull::build(varr, md); if (err == OK) { Vector<Vector3> points2; diff --git a/editor/plugins/abstract_polygon_2d_editor.cpp b/editor/plugins/abstract_polygon_2d_editor.cpp index 1abba45f9e..b905c8db12 100644 --- a/editor/plugins/abstract_polygon_2d_editor.cpp +++ b/editor/plugins/abstract_polygon_2d_editor.cpp @@ -31,6 +31,7 @@ #include "abstract_polygon_2d_editor.h" #include "canvas_item_editor_plugin.h" +#include "core/math/geometry_2d.h" #include "core/os/keyboard.h" #include "editor/editor_scale.h" @@ -671,7 +672,7 @@ AbstractPolygon2DEditor::PosVertex AbstractPolygon2DEditor::closest_edge_point(c Vector2 segment[2] = { xform.xform(points[i] + offset), xform.xform(points[(i + 1) % n_points] + offset) }; - Vector2 cp = Geometry::get_closest_point_to_segment_2d(p_pos, segment); + Vector2 cp = Geometry2D::get_closest_point_to_segment(p_pos, segment); if (cp.distance_squared_to(segment[0]) < eps2 || cp.distance_squared_to(segment[1]) < eps2) { continue; //not valid to reuse point diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp index df67482dfb..a7d5c2207b 100644 --- a/editor/plugins/animation_blend_space_2d_editor.cpp +++ b/editor/plugins/animation_blend_space_2d_editor.cpp @@ -32,7 +32,7 @@ #include "core/input/input.h" #include "core/io/resource_loader.h" -#include "core/math/delaunay_2d.h" +#include "core/math/geometry_2d.h" #include "core/os/keyboard.h" #include "core/project_settings.h" #include "editor/editor_scale.h" @@ -165,7 +165,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven triangle.push_back(points[idx]); } - if (Geometry::is_point_in_triangle(mb->get_position(), triangle[0], triangle[1], triangle[2])) { + if (Geometry2D::is_point_in_triangle(mb->get_position(), triangle[0], triangle[1], triangle[2])) { selected_triangle = i; _update_tool_erase(); return; diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp index 652754a146..a435b1c482 100644 --- a/editor/plugins/animation_state_machine_editor.cpp +++ b/editor/plugins/animation_state_machine_editor.cpp @@ -32,7 +32,7 @@ #include "core/input/input.h" #include "core/io/resource_loader.h" -#include "core/math/delaunay_2d.h" +#include "core/math/geometry_2d.h" #include "core/os/keyboard.h" #include "core/project_settings.h" #include "editor/editor_scale.h" @@ -191,7 +191,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv transition_lines[i].from, transition_lines[i].to }; - Vector2 cpoint = Geometry::get_closest_point_to_segment_2d(mb->get_position(), s); + Vector2 cpoint = Geometry2D::get_closest_point_to_segment(mb->get_position(), s); float d = cpoint.distance_to(mb->get_position()); if (d > transition_lines[i].width) { continue; diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 744c7907af..2f7080b1a5 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -31,6 +31,7 @@ #include "canvas_item_editor_plugin.h" #include "core/input/input.h" +#include "core/math/geometry_2d.h" #include "core/os/keyboard.h" #include "core/print_string.h" #include "core/project_settings.h" @@ -53,8 +54,10 @@ #include "scene/main/window.h" #include "scene/resources/packed_scene.h" -#define MIN_ZOOM 0.01 -#define MAX_ZOOM 100 +// Min and Max are power of two in order to play nicely with successive increment. +// That way, we can naturally reach a 100% zoom from boundaries. +#define MIN_ZOOM 1. / 128 +#define MAX_ZOOM 128 #define RULER_WIDTH (15 * EDSCALE) #define SCALE_HANDLE_DISTANCE 25 @@ -86,7 +89,6 @@ public: GridContainer *child_container; set_title(TTR("Configure Snap")); - get_ok()->set_text(TTR("Close")); container = memnew(VBoxContainer); add_child(container); @@ -672,7 +674,7 @@ void CanvasItemEditor::_get_bones_at_pos(const Point2 &p_pos, Vector<_SelectResu } // Check if the point is inside the Polygon2D - if (Geometry::is_point_in_polygon(screen_pos, bone_shape)) { + if (Geometry2D::is_point_in_polygon(screen_pos, bone_shape)) { // Check if the item is already in the list bool duplicate = false; for (int i = 0; i < r_items.size(); i++) { @@ -1213,7 +1215,11 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo view_offset.y += int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor(); update_viewport(); } else { - _zoom_on_position(zoom * (1 - (0.05 * b->get_factor())), b->get_position()); + float new_zoom = _get_next_zoom_value(-1); + if (b->get_factor() != 1.f) { + new_zoom = zoom * ((new_zoom / zoom - 1.f) * b->get_factor() + 1.f); + } + _zoom_on_position(new_zoom, b->get_position()); } return true; } @@ -1224,7 +1230,11 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo view_offset.y -= int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor(); update_viewport(); } else { - _zoom_on_position(zoom * ((0.95 + (0.05 * b->get_factor())) / 0.95), b->get_position()); + float new_zoom = _get_next_zoom_value(1); + if (b->get_factor() != 1.f) { + new_zoom = zoom * ((new_zoom / zoom - 1.f) * b->get_factor() + 1.f); + } + _zoom_on_position(new_zoom, b->get_position()); } return true; } @@ -2240,7 +2250,7 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { return true; } - if (k.is_valid() && !k->is_pressed() && drag_type == DRAG_KEY_MOVE && tool == TOOL_SELECT && + if (k.is_valid() && !k->is_pressed() && drag_type == DRAG_KEY_MOVE && (tool == TOOL_SELECT || tool == TOOL_MOVE) && (k->get_keycode() == KEY_UP || k->get_keycode() == KEY_DOWN || k->get_keycode() == KEY_LEFT || k->get_keycode() == KEY_RIGHT)) { // Confirm canvas items move by arrow keys if ((!Input::get_singleton()->is_key_pressed(KEY_UP)) && @@ -4333,8 +4343,37 @@ void CanvasItemEditor::_set_anchors_preset(Control::LayoutPreset p_preset) { undo_redo->commit_action(); } +float CanvasItemEditor::_get_next_zoom_value(int p_increment_count) const { + // Base increment factor defined as the twelveth root of two. + // This allow a smooth geometric evolution of the zoom, with the advantage of + // visiting all integer power of two scale factors. + // note: this is analogous to the 'semitones' interval in the music world + // In order to avoid numerical imprecisions, we compute and edit a zoom index + // with the following relation: zoom = 2 ^ (index / 12) + + if (zoom < CMP_EPSILON || p_increment_count == 0) { + return 1.f; + } + + // Remove Editor scale from the index computation + float zoom_noscale = zoom / MAX(1, EDSCALE); + + // zoom = 2**(index/12) => log2(zoom) = index/12 + float closest_zoom_index = Math::round(Math::log(zoom_noscale) * 12.f / Math::log(2.f)); + + float new_zoom_index = closest_zoom_index + p_increment_count; + float new_zoom = Math::pow(2.f, new_zoom_index / 12.f); + + // Restore Editor scale transformation + new_zoom *= MAX(1, EDSCALE); + + return new_zoom; +} + void CanvasItemEditor::_zoom_on_position(float p_zoom, Point2 p_position) { - if (p_zoom < MIN_ZOOM || p_zoom > MAX_ZOOM) { + p_zoom = CLAMP(p_zoom, MIN_ZOOM, MAX_ZOOM); + + if (p_zoom == zoom) { return; } @@ -4376,7 +4415,7 @@ void CanvasItemEditor::_update_zoom_label() { } void CanvasItemEditor::_button_zoom_minus() { - _zoom_on_position(zoom / Math_SQRT2, viewport_scrollable->get_size() / 2.0); + _zoom_on_position(_get_next_zoom_value(-6), viewport_scrollable->get_size() / 2.0); } void CanvasItemEditor::_button_zoom_reset() { @@ -4384,7 +4423,7 @@ void CanvasItemEditor::_button_zoom_reset() { } void CanvasItemEditor::_button_zoom_plus() { - _zoom_on_position(zoom * Math_SQRT2, viewport_scrollable->get_size() / 2.0); + _zoom_on_position(_get_next_zoom_value(6), viewport_scrollable->get_size() / 2.0); } void CanvasItemEditor::_button_toggle_smart_snap(bool p_status) { @@ -5610,6 +5649,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { hb->add_child(pan_button); pan_button->set_toggle_mode(true); pan_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select), make_binds(TOOL_PAN)); + pan_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/pan_mode", TTR("Pan Mode"), KEY_G)); pan_button->set_tooltip(TTR("Pan Mode")); ruler_button = memnew(ToolButton); diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index a686c98f65..765d5f81d0 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -528,6 +528,7 @@ private: VBoxContainer *controls_vb; HBoxContainer *zoom_hb; + float _get_next_zoom_value(int p_increment_count) const; void _zoom_on_position(float p_zoom, Point2 p_position = Point2()); void _update_zoom_label(); void _button_zoom_minus(); diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.cpp b/editor/plugins/collision_polygon_3d_editor_plugin.cpp index c61d410d38..d9d9cf6a87 100644 --- a/editor/plugins/collision_polygon_3d_editor_plugin.cpp +++ b/editor/plugins/collision_polygon_3d_editor_plugin.cpp @@ -32,6 +32,7 @@ #include "canvas_item_editor_plugin.h" #include "core/input/input.h" +#include "core/math/geometry_2d.h" #include "core/os/file_access.h" #include "core/os/keyboard.h" #include "editor/editor_settings.h" @@ -196,7 +197,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con p_camera->unproject_position(gt.xform(Vector3(poly[(i + 1) % poly.size()].x, poly[(i + 1) % poly.size()].y, depth))) }; - Vector2 cp = Geometry::get_closest_point_to_segment_2d(gpoint, points); + Vector2 cp = Geometry2D::get_closest_point_to_segment(gpoint, points); if (cp.distance_squared_to(points[0]) < CMP_EPSILON2 || cp.distance_squared_to(points[1]) < CMP_EPSILON2) { continue; //not valid to reuse point } diff --git a/editor/plugins/collision_shape_2d_editor_plugin.cpp b/editor/plugins/collision_shape_2d_editor_plugin.cpp index 0f381c06b4..596629f8e8 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.cpp +++ b/editor/plugins/collision_shape_2d_editor_plugin.cpp @@ -36,6 +36,7 @@ #include "scene/resources/concave_polygon_shape_2d.h" #include "scene/resources/convex_polygon_shape_2d.h" #include "scene/resources/line_shape_2d.h" +#include "scene/resources/ray_shape_2d.h" #include "scene/resources/rectangle_shape_2d.h" #include "scene/resources/segment_shape_2d.h" diff --git a/editor/plugins/debugger_editor_plugin.cpp b/editor/plugins/debugger_editor_plugin.cpp index 0ca479555d..0a4d173923 100644 --- a/editor/plugins/debugger_editor_plugin.cpp +++ b/editor/plugins/debugger_editor_plugin.cpp @@ -68,13 +68,10 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(EditorNode *p_editor, MenuButton *p_d p->add_check_shortcut(ED_SHORTCUT("editor/visible_navigation", TTR("Visible Navigation")), RUN_DEBUG_NAVIGATION); p->set_item_tooltip(p->get_item_count() - 1, TTR("Navigation meshes and polygons will be visible on the running game if this option is turned on.")); p->add_separator(); - //those are now on by default, since they are harmless p->add_check_shortcut(ED_SHORTCUT("editor/sync_scene_changes", TTR("Sync Scene Changes")), RUN_LIVE_DEBUG); p->set_item_tooltip(p->get_item_count() - 1, TTR("When this option is turned on, any changes made to the scene in the editor will be replicated in the running game.\nWhen used remotely on a device, this is more efficient with network filesystem.")); - p->set_item_checked(p->get_item_count() - 1, true); p->add_check_shortcut(ED_SHORTCUT("editor/sync_script_changes", TTR("Sync Script Changes")), RUN_RELOAD_SCRIPTS); p->set_item_tooltip(p->get_item_count() - 1, TTR("When this option is turned on, any script that is saved will be reloaded on the running game.\nWhen used remotely on a device, this is more efficient with network filesystem.")); - p->set_item_checked(p->get_item_count() - 1, true); // Multi-instance, start/stop instances_menu = memnew(PopupMenu); @@ -174,8 +171,8 @@ void DebuggerEditorPlugin::_update_debug_options() { bool check_file_server = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_file_server", false); bool check_debug_collisions = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_collisons", false); bool check_debug_navigation = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_navigation", false); - bool check_live_debug = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_live_debug", false); - bool check_reload_scripts = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_reload_scripts", false); + bool check_live_debug = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_live_debug", true); + bool check_reload_scripts = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_reload_scripts", true); int instances = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_instances", 1); if (check_deploy_remote) { diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 3c12022854..f59b4171b4 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -328,17 +328,13 @@ void Node3DEditorViewport::_update_camera(float p_interp_delta) { //------- // Apply camera transform - float tolerance = 0.001; + real_t tolerance = 0.001; bool equal = true; - if (Math::abs(old_camera_cursor.x_rot - camera_cursor.x_rot) > tolerance || Math::abs(old_camera_cursor.y_rot - camera_cursor.y_rot) > tolerance) { + if (!Math::is_equal_approx(old_camera_cursor.x_rot, camera_cursor.x_rot, tolerance) || !Math::is_equal_approx(old_camera_cursor.y_rot, camera_cursor.y_rot, tolerance)) { equal = false; - } - - if (equal && old_camera_cursor.pos.distance_squared_to(camera_cursor.pos) > tolerance * tolerance) { + } else if (!old_camera_cursor.pos.is_equal_approx(camera_cursor.pos)) { equal = false; - } - - if (equal && Math::abs(old_camera_cursor.distance - camera_cursor.distance) > tolerance) { + } else if (!Math::is_equal_approx(old_camera_cursor.distance, camera_cursor.distance, tolerance)) { equal = false; } @@ -356,6 +352,7 @@ void Node3DEditorViewport::_update_camera(float p_interp_delta) { update_transform_gizmo_view(); rotation_control->update(); } + spatial_editor->update_grid(); } Transform Node3DEditorViewport::to_camera_transform(const Cursor &p_cursor) const { @@ -835,7 +832,7 @@ bool Node3DEditorViewport::_gizmo_select(const Vector2 &p_screenpos, bool p_high Vector3 r; - if (Geometry::segment_intersects_sphere(ray_pos, ray_pos + ray * MAX_Z, grabber_pos, grabber_radius, &r)) { + if (Geometry3D::segment_intersects_sphere(ray_pos, ray_pos + ray * MAX_Z, grabber_pos, grabber_radius, &r)) { float d = r.distance_to(ray_pos); if (d < col_d) { col_d = d; @@ -932,7 +929,7 @@ bool Node3DEditorViewport::_gizmo_select(const Vector2 &p_screenpos, bool p_high Vector3 r; - if (Geometry::segment_intersects_sphere(ray_pos, ray_pos + ray * MAX_Z, grabber_pos, grabber_radius, &r)) { + if (Geometry3D::segment_intersects_sphere(ray_pos, ray_pos + ray * MAX_Z, grabber_pos, grabber_radius, &r)) { float d = r.distance_to(ray_pos); if (d < col_d) { col_d = d; @@ -4929,8 +4926,10 @@ void Node3DEditor::_menu_item_pressed(int p_option) { for (int i = 0; i < 3; ++i) { if (grid_enable[i]) { - RenderingServer::get_singleton()->instance_set_visible(grid_instance[i], grid_enabled); grid_visible[i] = grid_enabled; + if (grid_instance[i].is_valid()) { + RenderingServer::get_singleton()->instance_set_visible(grid_instance[i], grid_enabled); + } } } @@ -5054,6 +5053,7 @@ void Node3DEditor::_init_indicators() { indicator_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); indicator_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); indicator_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + indicator_mat->set_transparency(StandardMaterial3D::Transparency::TRANSPARENCY_ALPHA_DEPTH_PRE_PASS); Vector<Color> origin_colors; Vector<Vector3> origin_points; @@ -5082,12 +5082,27 @@ void Node3DEditor::_init_indicators() { origin_colors.push_back(origin_color); origin_colors.push_back(origin_color); - origin_points.push_back(axis * 4096); - origin_points.push_back(axis * -4096); - } - - grid_enable[1] = true; - grid_visible[1] = true; + origin_colors.push_back(origin_color); + origin_colors.push_back(origin_color); + origin_colors.push_back(origin_color); + origin_colors.push_back(origin_color); + // To both allow having a large origin size and avoid jitter + // at small scales, we should segment the line into pieces. + // 3 pieces seems to do the trick, and let's use powers of 2. + origin_points.push_back(axis * 1048576); + origin_points.push_back(axis * 1024); + origin_points.push_back(axis * 1024); + origin_points.push_back(axis * -1024); + origin_points.push_back(axis * -1024); + origin_points.push_back(axis * -1048576); + } + + grid_enable[0] = EditorSettings::get_singleton()->get("editors/3d/grid_xy_plane"); + grid_enable[1] = EditorSettings::get_singleton()->get("editors/3d/grid_yz_plane"); + grid_enable[2] = EditorSettings::get_singleton()->get("editors/3d/grid_xz_plane"); + grid_visible[0] = grid_enable[0]; + grid_visible[1] = grid_enable[1]; + grid_visible[2] = grid_enable[2]; _init_grid(); @@ -5418,6 +5433,15 @@ void Node3DEditor::_update_gizmos_menu_theme() { } void Node3DEditor::_init_grid() { + if (!grid_enabled) { + return; + } + Camera3D *camera = get_editor_viewport(0)->camera; + Vector3 camera_position = camera->get_translation(); + if (camera_position == Vector3()) { + return; // Camera3D is invalid, don't draw the grid. + } + Vector<Color> grid_colors[3]; Vector<Vector3> grid_points[3]; @@ -5426,52 +5450,111 @@ void Node3DEditor::_init_grid() { int grid_size = EditorSettings::get_singleton()->get("editors/3d/grid_size"); int primary_grid_steps = EditorSettings::get_singleton()->get("editors/3d/primary_grid_steps"); - for (int i = 0; i < 3; i++) { - Vector3 axis; - axis[i] = 1; - Vector3 axis_n1; - axis_n1[(i + 1) % 3] = 1; - Vector3 axis_n2; - axis_n2[(i + 2) % 3] = 1; - - for (int j = -grid_size; j <= grid_size; j++) { - Vector3 p1 = axis_n1 * j + axis_n2 * -grid_size; - Vector3 p1_dest = p1 * (-axis_n2 + axis_n1); - Vector3 p2 = axis_n2 * j + axis_n1 * -grid_size; - Vector3 p2_dest = p2 * (-axis_n1 + axis_n2); - - Color line_color = secondary_grid_color; - if (origin_enabled && j == 0) { - // Don't draw the center lines of the grid if the origin is enabled - // The origin would overlap the grid lines in this case, causing flickering - continue; - } else if (j % primary_grid_steps == 0) { - line_color = primary_grid_color; + // Which grid planes are enabled? Which should we generate? + grid_enable[0] = grid_visible[0] = EditorSettings::get_singleton()->get("editors/3d/grid_xy_plane"); + grid_enable[1] = grid_visible[1] = EditorSettings::get_singleton()->get("editors/3d/grid_yz_plane"); + grid_enable[2] = grid_visible[2] = EditorSettings::get_singleton()->get("editors/3d/grid_xz_plane"); + + // Offsets division_level for bigger or smaller grids. + // Default value is -0.2. -1.0 gives Blender-like behavior, 0.5 gives huge grids. + real_t division_level_bias = EditorSettings::get_singleton()->get("editors/3d/grid_division_level_bias"); + // Default largest grid size is 100m, 10^2 (default value is 2). + int division_level_max = EditorSettings::get_singleton()->get("editors/3d/grid_division_level_max"); + // Default smallest grid size is 1cm, 10^-2 (default value is -2). + int division_level_min = EditorSettings::get_singleton()->get("editors/3d/grid_division_level_min"); + ERR_FAIL_COND_MSG(division_level_max < division_level_min, "The 3D grid's maximum division level cannot be lower than its minimum division level."); + + if (primary_grid_steps != 10) { // Log10 of 10 is 1. + // Change of base rule, divide by ln(10). + real_t div = Math::log((real_t)primary_grid_steps) / (real_t)2.302585092994045901094; + // Trucation (towards zero) is intentional. + division_level_max = (int)(division_level_max / div); + division_level_min = (int)(division_level_min / div); + } + + for (int a = 0; a < 3; a++) { + if (!grid_enable[a]) { + continue; // If this grid plane is disabled, skip generation. + } + int b = (a + 1) % 3; + int c = (a + 2) % 3; + + real_t division_level = Math::log(Math::abs(camera_position[c])) / Math::log((double)primary_grid_steps) + division_level_bias; + division_level = CLAMP(division_level, division_level_min, division_level_max); + real_t division_level_floored = Math::floor(division_level); + real_t division_level_decimals = division_level - division_level_floored; + + real_t small_step_size = Math::pow(primary_grid_steps, division_level_floored); + real_t large_step_size = small_step_size * primary_grid_steps; + real_t center_a = large_step_size * (int)(camera_position[a] / large_step_size); + real_t center_b = large_step_size * (int)(camera_position[b] / large_step_size); + + real_t bgn_a = center_a - grid_size * small_step_size; + real_t end_a = center_a + grid_size * small_step_size; + real_t bgn_b = center_b - grid_size * small_step_size; + real_t end_b = center_b + grid_size * small_step_size; + + // In each iteration of this loop, draw one line in each direction (so two lines per loop, in each if statement). + for (int i = -grid_size; i <= grid_size; i++) { + Color line_color; + // Is this a primary line? Set the appropriate color. + if (i % primary_grid_steps == 0) { + line_color = primary_grid_color.lerp(secondary_grid_color, division_level_decimals); + } else { + line_color = secondary_grid_color; + line_color.a = line_color.a * (1 - division_level_decimals); + } + // Makes lines farther from the center fade out. + // Due to limitations of lines, any that come near the camera have full opacity always. + // This should eventually be replaced by some kind of "distance fade" system, outside of this function. + // But the effect is still somewhat convincing... + line_color.a *= 1 - (1 - division_level_decimals * 0.9) * (Math::abs(i / (float)grid_size)); + + real_t position_a = center_a + i * small_step_size; + real_t position_b = center_b + i * small_step_size; + + // Don't draw lines over the origin if it's enabled. + if (!(origin_enabled && Math::is_zero_approx(position_a))) { + Vector3 line_bgn = Vector3(); + Vector3 line_end = Vector3(); + line_bgn[a] = position_a; + line_end[a] = position_a; + line_bgn[b] = bgn_b; + line_end[b] = end_b; + grid_points[c].push_back(line_bgn); + grid_points[c].push_back(line_end); + grid_colors[c].push_back(line_color); + grid_colors[c].push_back(line_color); } - grid_points[i].push_back(p1); - grid_points[i].push_back(p1_dest); - grid_colors[i].push_back(line_color); - grid_colors[i].push_back(line_color); - - grid_points[i].push_back(p2); - grid_points[i].push_back(p2_dest); - grid_colors[i].push_back(line_color); - grid_colors[i].push_back(line_color); + if (!(origin_enabled && Math::is_zero_approx(position_b))) { + Vector3 line_bgn = Vector3(); + Vector3 line_end = Vector3(); + line_bgn[b] = position_b; + line_end[b] = position_b; + line_bgn[a] = bgn_a; + line_end[a] = end_a; + grid_points[c].push_back(line_bgn); + grid_points[c].push_back(line_end); + grid_colors[c].push_back(line_color); + grid_colors[c].push_back(line_color); + } } - grid[i] = RenderingServer::get_singleton()->mesh_create(); + // Create a mesh from the pushed vector points and colors. + grid[c] = RenderingServer::get_singleton()->mesh_create(); Array d; d.resize(RS::ARRAY_MAX); - d[RenderingServer::ARRAY_VERTEX] = grid_points[i]; - d[RenderingServer::ARRAY_COLOR] = grid_colors[i]; - RenderingServer::get_singleton()->mesh_add_surface_from_arrays(grid[i], RenderingServer::PRIMITIVE_LINES, d); - RenderingServer::get_singleton()->mesh_surface_set_material(grid[i], 0, indicator_mat->get_rid()); - grid_instance[i] = RenderingServer::get_singleton()->instance_create2(grid[i], get_tree()->get_root()->get_world_3d()->get_scenario()); + d[RenderingServer::ARRAY_VERTEX] = grid_points[c]; + d[RenderingServer::ARRAY_COLOR] = grid_colors[c]; + RenderingServer::get_singleton()->mesh_add_surface_from_arrays(grid[c], RenderingServer::PRIMITIVE_LINES, d); + RenderingServer::get_singleton()->mesh_surface_set_material(grid[c], 0, indicator_mat->get_rid()); + grid_instance[c] = RenderingServer::get_singleton()->instance_create2(grid[c], get_tree()->get_root()->get_world_3d()->get_scenario()); - RenderingServer::get_singleton()->instance_set_visible(grid_instance[i], grid_visible[i]); - RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(grid_instance[i], RS::SHADOW_CASTING_SETTING_OFF); - RS::get_singleton()->instance_set_layer_mask(grid_instance[i], 1 << Node3DEditorViewport::GIZMO_GRID_LAYER); + // Yes, the end of this line is supposed to be a. + RenderingServer::get_singleton()->instance_set_visible(grid_instance[c], grid_visible[a]); + RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(grid_instance[c], RS::SHADOW_CASTING_SETTING_OFF); + RS::get_singleton()->instance_set_layer_mask(grid_instance[c], 1 << Node3DEditorViewport::GIZMO_GRID_LAYER); } } @@ -5489,6 +5572,11 @@ void Node3DEditor::_finish_grid() { } } +void Node3DEditor::update_grid() { + _finish_grid(); + _init_grid(); +} + bool Node3DEditor::is_any_freelook_active() const { for (unsigned int i = 0; i < VIEWPORTS_COUNT; ++i) { if (viewports[i]->is_freelook_active()) { diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index 3d92e7e7e1..32b087c372 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -766,6 +766,7 @@ public: Ref<ArrayMesh> get_scale_gizmo(int idx) const { return scale_gizmo[idx]; } Ref<ArrayMesh> get_scale_plane_gizmo(int idx) const { return scale_plane_gizmo[idx]; } + void update_grid(); void update_transform_gizmo(); void update_all_gizmos(Node *p_node = nullptr); void snap_selected_nodes_to_floor(); diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index a44fe69ff6..25cffa3d6c 100644 --- a/editor/plugins/path_3d_editor_plugin.cpp +++ b/editor/plugins/path_3d_editor_plugin.cpp @@ -30,6 +30,8 @@ #include "path_3d_editor_plugin.h" +#include "core/math/geometry_2d.h" +#include "core/math/geometry_3d.h" #include "core/os/keyboard.h" #include "node_3d_editor_plugin.h" #include "scene/resources/curve.h" @@ -344,7 +346,7 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref Vector2 s[2]; s[0] = p_camera->unproject_position(from); s[1] = p_camera->unproject_position(to); - Vector2 inters = Geometry::get_closest_point_to_segment_2d(mbpos, s); + Vector2 inters = Geometry2D::get_closest_point_to_segment(mbpos, s); float d = inters.distance_to(mbpos); if (d < 10 && d < closest_d) { @@ -354,7 +356,7 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref Vector3 ray_dir = p_camera->project_ray_normal(mbpos); Vector3 ra, rb; - Geometry::get_closest_points_between_segments(ray_from, ray_from + ray_dir * 4096, from, to, ra, rb); + Geometry3D::get_closest_points_between_segments(ray_from, ray_from + ray_dir * 4096, from, to, ra, rb); closest_seg_point = it.xform(rb); } diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp index 7ee695b9fe..1f98c0139b 100644 --- a/editor/plugins/polygon_2d_editor_plugin.cpp +++ b/editor/plugins/polygon_2d_editor_plugin.cpp @@ -32,6 +32,7 @@ #include "canvas_item_editor_plugin.h" #include "core/input/input.h" +#include "core/math/geometry_2d.h" #include "core/os/file_access.h" #include "core/os/keyboard.h" #include "editor/editor_scale.h" @@ -693,7 +694,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { polys.write[j] = mtx.xform(points_prev[idx]); } - if (Geometry::is_point_in_polygon(Vector2(mb->get_position().x, mb->get_position().y), polys)) { + if (Geometry2D::is_point_in_polygon(Vector2(mb->get_position().x, mb->get_position().y), polys)) { erase_index = i; break; } diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index e7f8a56e5e..4b79d8c344 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -609,6 +609,18 @@ void ScriptTextEditor::_validate_script() { for (List<ScriptLanguage::Warning>::Element *E = warnings.front(); E; E = E->next()) { ScriptLanguage::Warning w = E->get(); + Dictionary ignore_meta; + ignore_meta["line"] = w.line; + ignore_meta["code"] = w.string_code.to_lower(); + warnings_panel->push_cell(); + warnings_panel->push_meta(ignore_meta); + warnings_panel->push_color( + warnings_panel->get_theme_color("accent_color", "Editor").lerp(warnings_panel->get_theme_color("mono_color", "Editor"), 0.5)); + warnings_panel->add_text(TTR("[Ignore]")); + warnings_panel->pop(); // Color. + warnings_panel->pop(); // Meta ignore. + warnings_panel->pop(); // Cell. + warnings_panel->push_cell(); warnings_panel->push_meta(w.line - 1); warnings_panel->push_color(warnings_panel->get_theme_color("warning_color", "Editor")); @@ -621,15 +633,6 @@ void ScriptTextEditor::_validate_script() { warnings_panel->push_cell(); warnings_panel->add_text(w.message); warnings_panel->pop(); // Cell. - - Dictionary ignore_meta; - ignore_meta["line"] = w.line; - ignore_meta["code"] = w.string_code.to_lower(); - warnings_panel->push_cell(); - warnings_panel->push_meta(ignore_meta); - warnings_panel->add_text(TTR("(ignore)")); - warnings_panel->pop(); // Meta ignore. - warnings_panel->pop(); // Cell. } warnings_panel->pop(); // Table. @@ -1747,6 +1750,8 @@ ScriptTextEditor::ScriptTextEditor() { warnings_panel = memnew(RichTextLabel); editor_box->add_child(warnings_panel); + warnings_panel->add_theme_font_override( + "normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font("main", "EditorFonts")); warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE)); warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL); warnings_panel->set_meta_underline(true); diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index c256acd17b..321b4432ab 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -30,13 +30,385 @@ #include "skeleton_3d_editor_plugin.h" +#include "core/io/resource_saver.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_properties.h" +#include "editor/editor_scale.h" +#include "editor/plugins/animation_player_editor_plugin.h" #include "node_3d_editor_plugin.h" #include "scene/3d/collision_shape_3d.h" +#include "scene/3d/mesh_instance_3d.h" #include "scene/3d/physics_body_3d.h" #include "scene/3d/physics_joint_3d.h" #include "scene/resources/capsule_shape_3d.h" #include "scene/resources/sphere_shape_3d.h" +void BoneTransformEditor::create_editors() { + const Color section_color = get_theme_color("prop_subsection", "Editor"); + + section = memnew(EditorInspectorSection); + section->setup("trf_properties", label, this, section_color, true); + add_child(section); + + key_button = memnew(Button); + key_button->set_text(TTR("Key Transform")); + key_button->set_visible(keyable); + key_button->set_icon(get_theme_icon("Key", "EditorIcons")); + key_button->set_flat(true); + section->get_vbox()->add_child(key_button); + + enabled_checkbox = memnew(CheckBox(TTR("Pose Enabled"))); + enabled_checkbox->set_flat(true); + enabled_checkbox->set_visible(toggle_enabled); + section->get_vbox()->add_child(enabled_checkbox); + + Label *l1 = memnew(Label(TTR("Translation"))); + section->get_vbox()->add_child(l1); + + translation_grid = memnew(GridContainer()); + translation_grid->set_columns(TRANSLATION_COMPONENTS); + section->get_vbox()->add_child(translation_grid); + + Label *l2 = memnew(Label(TTR("Rotation Degrees"))); + section->get_vbox()->add_child(l2); + + rotation_grid = memnew(GridContainer()); + rotation_grid->set_columns(ROTATION_DEGREES_COMPONENTS); + section->get_vbox()->add_child(rotation_grid); + + Label *l3 = memnew(Label(TTR("Scale"))); + section->get_vbox()->add_child(l3); + + scale_grid = memnew(GridContainer()); + scale_grid->set_columns(SCALE_COMPONENTS); + section->get_vbox()->add_child(scale_grid); + + Label *l4 = memnew(Label(TTR("Transform"))); + section->get_vbox()->add_child(l4); + + transform_grid = memnew(GridContainer()); + transform_grid->set_columns(TRANSFORM_CONTROL_COMPONENTS); + section->get_vbox()->add_child(transform_grid); + + static const char *desc[TRANSFORM_COMPONENTS] = { "x", "y", "z", "x", "y", "z", "x", "y", "z", "x", "y", "z" }; + + for (int i = 0; i < TRANSFORM_CONTROL_COMPONENTS; ++i) { + translation_slider[i] = memnew(EditorSpinSlider()); + translation_slider[i]->set_label(desc[i]); + setup_spinner(translation_slider[i], false); + translation_grid->add_child(translation_slider[i]); + + rotation_slider[i] = memnew(EditorSpinSlider()); + rotation_slider[i]->set_label(desc[i]); + setup_spinner(rotation_slider[i], false); + rotation_grid->add_child(rotation_slider[i]); + + scale_slider[i] = memnew(EditorSpinSlider()); + scale_slider[i]->set_label(desc[i]); + setup_spinner(scale_slider[i], false); + scale_grid->add_child(scale_slider[i]); + } + + for (int i = 0; i < TRANSFORM_COMPONENTS; ++i) { + transform_slider[i] = memnew(EditorSpinSlider()); + transform_slider[i]->set_label(desc[i]); + setup_spinner(transform_slider[i], true); + transform_grid->add_child(transform_slider[i]); + } +} + +void BoneTransformEditor::setup_spinner(EditorSpinSlider *spinner, const bool is_transform_spinner) { + spinner->set_flat(true); + spinner->set_min(-10000); + spinner->set_max(10000); + spinner->set_step(0.001f); + spinner->set_hide_slider(true); + spinner->set_allow_greater(true); + spinner->set_allow_lesser(true); + spinner->set_h_size_flags(SIZE_EXPAND_FILL); + + spinner->connect_compat("value_changed", this, "_value_changed", varray(is_transform_spinner)); +} + +void BoneTransformEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + create_editors(); + key_button->connect_compat("pressed", this, "_key_button_pressed"); + enabled_checkbox->connect_compat("toggled", this, "_checkbox_toggled"); + [[fallthrough]]; + } + case NOTIFICATION_THEME_CHANGED: { + const Color base = get_theme_color("accent_color", "Editor"); + const Color bg_color = get_theme_color("property_color", "Editor"); + const Color bg_lbl_color(bg_color.r, bg_color.g, bg_color.b, 0.5); + + for (int i = 0; i < TRANSLATION_COMPONENTS; i++) { + Color c = base; + c.set_hsv(float(i % TRANSLATION_COMPONENTS) / TRANSLATION_COMPONENTS + 0.05, c.get_s() * 0.75, c.get_v()); + if (!translation_slider[i]) { + continue; + } + translation_slider[i]->set_custom_label_color(true, c); + } + + for (int i = 0; i < ROTATION_DEGREES_COMPONENTS; i++) { + Color c = base; + c.set_hsv(float(i % ROTATION_DEGREES_COMPONENTS) / ROTATION_DEGREES_COMPONENTS + 0.05, c.get_s() * 0.75, c.get_v()); + if (!rotation_slider[i]) { + continue; + } + rotation_slider[i]->set_custom_label_color(true, c); + } + + for (int i = 0; i < SCALE_COMPONENTS; i++) { + Color c = base; + c.set_hsv(float(i % SCALE_COMPONENTS) / SCALE_COMPONENTS + 0.05, c.get_s() * 0.75, c.get_v()); + if (!scale_slider[i]) { + continue; + } + scale_slider[i]->set_custom_label_color(true, c); + } + + for (int i = 0; i < TRANSFORM_COMPONENTS; i++) { + Color c = base; + c.set_hsv(float(i % TRANSFORM_COMPONENTS) / TRANSFORM_COMPONENTS + 0.05, c.get_s() * 0.75, c.get_v()); + if (!transform_slider[i]) { + continue; + } + transform_slider[i]->set_custom_label_color(true, c); + } + + break; + } + case NOTIFICATION_SORT_CHILDREN: { + const Ref<Font> font = get_theme_font("font", "Tree"); + + Point2 buffer; + buffer.x += get_theme_constant("inspector_margin", "Editor"); + buffer.y += font->get_height(); + buffer.y += get_theme_constant("vseparation", "Tree"); + + const float vector_height = translation_grid->get_size().y; + const float transform_height = transform_grid->get_size().y; + const float button_height = key_button->get_size().y; + + const float width = get_size().x - get_theme_constant("inspector_margin", "Editor"); + Vector<Rect2> input_rects; + if (keyable && section->get_vbox()->is_visible()) { + input_rects.push_back(Rect2(key_button->get_position() + buffer, Size2(width, button_height))); + } else { + input_rects.push_back(Rect2(0, 0, 0, 0)); + } + + if (section->get_vbox()->is_visible()) { + input_rects.push_back(Rect2(translation_grid->get_position() + buffer, Size2(width, vector_height))); + input_rects.push_back(Rect2(rotation_grid->get_position() + buffer, Size2(width, vector_height))); + input_rects.push_back(Rect2(scale_grid->get_position() + buffer, Size2(width, vector_height))); + input_rects.push_back(Rect2(transform_grid->get_position() + buffer, Size2(width, transform_height))); + } else { + const int32_t start = input_rects.size(); + const int32_t empty_input_rect_elements = 4; + const int32_t end = start + empty_input_rect_elements; + for (int i = start; i < end; ++i) { + input_rects.push_back(Rect2(0, 0, 0, 0)); + } + } + + for (int32_t i = 0; i < input_rects.size(); i++) { + background_rects[i] = input_rects[i]; + } + + update(); + break; + } + case NOTIFICATION_DRAW: { + const Color dark_color = get_theme_color("dark_color_2", "Editor"); + + for (int i = 0; i < 5; ++i) { + draw_rect(background_rects[i], dark_color); + } + + break; + } + } +} + +void BoneTransformEditor::_value_changed(const double p_value, const bool p_from_transform) { + if (updating) + return; + + if (property.get_slicec('/', 0) == "bones" && property.get_slicec('/', 2) == "custom_pose") { + const Transform tform = compute_transform(p_from_transform); + + undo_redo->create_action(TTR("Set Custom Bone Pose Transform"), UndoRedo::MERGE_ENDS); + undo_redo->add_undo_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), skeleton->get_bone_custom_pose(property.get_slicec('/', 1).to_int())); + undo_redo->add_do_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), tform); + undo_redo->commit_action(); + } else if (property.get_slicec('/', 0) == "bones") { + const Transform tform = compute_transform(p_from_transform); + + undo_redo->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); + undo_redo->add_undo_property(skeleton, property, skeleton->get(property)); + undo_redo->add_do_property(skeleton, property, tform); + undo_redo->commit_action(); + } +} + +Transform BoneTransformEditor::compute_transform(const bool p_from_transform) const { + // Last modified was a raw transform column... + if (p_from_transform) { + Transform tform; + + for (int i = 0; i < BASIS_COMPONENTS; ++i) { + tform.basis[i / BASIS_SPLIT_COMPONENTS][i % BASIS_SPLIT_COMPONENTS] = transform_slider[i]->get_value(); + } + + for (int i = 0; i < TRANSLATION_COMPONENTS; ++i) { + tform.origin[i] = transform_slider[i + BASIS_COMPONENTS]->get_value(); + } + + return tform; + } + + return Transform( + Basis(Vector3(Math::deg2rad(rotation_slider[0]->get_value()), Math::deg2rad(rotation_slider[1]->get_value()), Math::deg2rad(rotation_slider[2]->get_value())), + Vector3(scale_slider[0]->get_value(), scale_slider[1]->get_value(), scale_slider[2]->get_value())), + Vector3(translation_slider[0]->get_value(), translation_slider[1]->get_value(), translation_slider[2]->get_value())); +} + +void BoneTransformEditor::update_enabled_checkbox() { + if (enabled_checkbox) { + const String path = "bones/" + property.get_slicec('/', 1) + "/enabled"; + const bool is_enabled = skeleton->get(path); + enabled_checkbox->set_pressed(is_enabled); + } +} + +void BoneTransformEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_value_changed", "value"), &BoneTransformEditor::_value_changed); + ClassDB::bind_method(D_METHOD("_key_button_pressed"), &BoneTransformEditor::_key_button_pressed); + ClassDB::bind_method(D_METHOD("_checkbox_toggled", "toggled"), &BoneTransformEditor::_checkbox_toggled); +} + +void BoneTransformEditor::_update_properties() { + if (updating) + return; + + if (skeleton == nullptr) + return; + + updating = true; + + Transform tform = skeleton->get(property); + _update_transform_properties(tform); +} + +void BoneTransformEditor::_update_custom_pose_properties() { + if (updating) + return; + + if (skeleton == nullptr) + return; + + updating = true; + + Transform tform = skeleton->get_bone_custom_pose(property.to_int()); + _update_transform_properties(tform); +} + +void BoneTransformEditor::_update_transform_properties(Transform tform) { + Quat rot = tform.get_basis(); + Vector3 rot_rad = rot.get_euler(); + Vector3 rot_degrees = Vector3(Math::rad2deg(rot_rad.x), Math::rad2deg(rot_rad.y), Math::rad2deg(rot_rad.z)); + Vector3 tr = tform.get_origin(); + Vector3 scale = tform.basis.get_scale(); + + for (int i = 0; i < TRANSLATION_COMPONENTS; i++) { + translation_slider[i]->set_value(tr[i]); + } + + for (int i = 0; i < ROTATION_DEGREES_COMPONENTS; i++) { + rotation_slider[i]->set_value(rot_degrees[i]); + } + + for (int i = 0; i < SCALE_COMPONENTS; i++) { + scale_slider[i]->set_value(scale[i]); + } + + transform_slider[0]->set_value(tform.get_basis()[Vector3::AXIS_X].x); + transform_slider[1]->set_value(tform.get_basis()[Vector3::AXIS_X].y); + transform_slider[2]->set_value(tform.get_basis()[Vector3::AXIS_X].z); + transform_slider[3]->set_value(tform.get_basis()[Vector3::AXIS_Y].x); + transform_slider[4]->set_value(tform.get_basis()[Vector3::AXIS_Y].y); + transform_slider[5]->set_value(tform.get_basis()[Vector3::AXIS_Y].z); + transform_slider[6]->set_value(tform.get_basis()[Vector3::AXIS_Z].x); + transform_slider[7]->set_value(tform.get_basis()[Vector3::AXIS_Z].y); + transform_slider[8]->set_value(tform.get_basis()[Vector3::AXIS_Z].z); + + for (int i = 0; i < TRANSLATION_COMPONENTS; i++) { + transform_slider[BASIS_COMPONENTS + i]->set_value(tform.get_origin()[i]); + } + + update_enabled_checkbox(); + updating = false; +} + +BoneTransformEditor::BoneTransformEditor(Skeleton3D *p_skeleton) : + translation_slider(), + rotation_slider(), + scale_slider(), + transform_slider(), + skeleton(p_skeleton), + key_button(nullptr), + enabled_checkbox(nullptr), + keyable(false), + toggle_enabled(false), + updating(false) { + undo_redo = EditorNode::get_undo_redo(); +} + +void BoneTransformEditor::set_target(const String &p_prop) { + property = p_prop; +} + +void BoneTransformEditor::set_keyable(const bool p_keyable) { + keyable = p_keyable; + if (key_button) { + key_button->set_visible(p_keyable); + } +} + +void BoneTransformEditor::set_toggle_enabled(const bool p_enabled) { + toggle_enabled = p_enabled; + if (enabled_checkbox) { + enabled_checkbox->set_visible(p_enabled); + } +} + +void BoneTransformEditor::_key_button_pressed() { + if (skeleton == nullptr) + return; + + const BoneId bone_id = property.get_slicec('/', 1).to_int(); + const String name = skeleton->get_bone_name(bone_id); + + if (name.empty()) + return; + + // Need to normalize the basis before you key it + Transform tform = compute_transform(true); + tform.orthonormalize(); + AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(skeleton, name, tform); +} + +void BoneTransformEditor::_checkbox_toggled(const bool p_toggled) { + if (enabled_checkbox) { + const String path = "bones/" + property.get_slicec('/', 1) + "/enabled"; + skeleton->set(path, p_toggled); + } +} + void Skeleton3DEditor::_on_click_option(int p_option) { if (!skeleton) { return; @@ -45,12 +417,14 @@ void Skeleton3DEditor::_on_click_option(int p_option) { switch (p_option) { case MENU_OPTION_CREATE_PHYSICAL_SKELETON: { create_physical_skeleton(); - } break; + break; + } } } void Skeleton3DEditor::create_physical_skeleton() { UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ERR_FAIL_COND(!get_tree()); Node *owner = skeleton == get_tree()->get_edited_scene_root() ? skeleton : skeleton->get_owner(); const int bc = skeleton->get_bone_count(); @@ -124,28 +498,164 @@ PhysicalBone3D *Skeleton3DEditor::create_physical_bone(int bone_id, int bone_chi return physical_bone; } -void Skeleton3DEditor::edit(Skeleton3D *p_node) { - skeleton = p_node; +Variant Skeleton3DEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { + TreeItem *selected = joint_tree->get_selected(); + + if (!selected) + return Variant(); + + Ref<Texture> icon = selected->get_icon(0); + + VBoxContainer *vb = memnew(VBoxContainer); + HBoxContainer *hb = memnew(HBoxContainer); + TextureRect *tf = memnew(TextureRect); + tf->set_texture(icon); + tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + hb->add_child(tf); + Label *label = memnew(Label(selected->get_text(0))); + hb->add_child(label); + vb->add_child(hb); + hb->set_modulate(Color(1, 1, 1, 1)); + + set_drag_preview(vb); + Dictionary drag_data; + drag_data["type"] = "nodes"; + drag_data["node"] = selected; + + return drag_data; } -void Skeleton3DEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - get_tree()->connect("node_removed", callable_mp(this, &Skeleton3DEditor::_node_removed)); +bool Skeleton3DEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { + TreeItem *target = joint_tree->get_item_at_position(p_point); + if (!target) + return false; + + const String path = target->get_metadata(0); + if (!path.begins_with("bones/")) + return false; + + TreeItem *selected = Object::cast_to<TreeItem>(Dictionary(p_data)["node"]); + if (target == selected) + return false; + + const String path2 = target->get_metadata(0); + if (!path2.begins_with("bones/")) + return false; + + return true; +} + +void Skeleton3DEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { + if (!can_drop_data_fw(p_point, p_data, p_from)) + return; + + TreeItem *target = joint_tree->get_item_at_position(p_point); + TreeItem *selected = Object::cast_to<TreeItem>(Dictionary(p_data)["node"]); + + const BoneId target_boneidx = String(target->get_metadata(0)).get_slicec('/', 1).to_int(); + const BoneId selected_boneidx = String(selected->get_metadata(0)).get_slicec('/', 1).to_int(); + + move_skeleton_bone(skeleton->get_path(), selected_boneidx, target_boneidx); +} + +void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_selected_boneidx, int32_t p_target_boneidx) { + Node *node = get_node_or_null(p_skeleton_path); + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(node); + ERR_FAIL_NULL(skeleton); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Set Bone Parentage")); + // If the target is a child of ourselves, we move only *us* and not our children + if (skeleton->is_bone_parent_of(p_target_boneidx, p_selected_boneidx)) { + const BoneId parent_idx = skeleton->get_bone_parent(p_selected_boneidx); + const int bone_count = skeleton->get_bone_count(); + for (BoneId i = 0; i < bone_count; ++i) { + if (skeleton->get_bone_parent(i) == p_selected_boneidx) { + ur->add_undo_method(skeleton, "set_bone_parent", i, skeleton->get_bone_parent(i)); + ur->add_do_method(skeleton, "set_bone_parent", i, parent_idx); + skeleton->set_bone_parent(i, parent_idx); + } + } } + ur->add_undo_method(skeleton, "set_bone_parent", p_selected_boneidx, skeleton->get_bone_parent(p_selected_boneidx)); + ur->add_do_method(skeleton, "set_bone_parent", p_selected_boneidx, p_target_boneidx); + skeleton->set_bone_parent(p_selected_boneidx, p_target_boneidx); + + update_joint_tree(); + ur->commit_action(); } -void Skeleton3DEditor::_node_removed(Node *p_node) { - if (p_node == skeleton) { - skeleton = nullptr; - options->hide(); +void Skeleton3DEditor::_joint_tree_selection_changed() { + TreeItem *selected = joint_tree->get_selected(); + const String path = selected->get_metadata(0); + + if (path.begins_with("bones/")) { + const int b_idx = path.get_slicec('/', 1).to_int(); + const String bone_path = "bones/" + itos(b_idx) + "/"; + + pose_editor->set_target(bone_path + "pose"); + rest_editor->set_target(bone_path + "rest"); + custom_pose_editor->set_target(bone_path + "custom_pose"); + + pose_editor->set_visible(true); + rest_editor->set_visible(true); + custom_pose_editor->set_visible(true); } } -void Skeleton3DEditor::_bind_methods() { +void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos) { +} + +void Skeleton3DEditor::_update_properties() { + if (rest_editor) + rest_editor->_update_properties(); + if (pose_editor) + pose_editor->_update_properties(); + if (custom_pose_editor) + custom_pose_editor->_update_custom_pose_properties(); +} + +void Skeleton3DEditor::update_joint_tree() { + joint_tree->clear(); + + if (skeleton == nullptr) + return; + + TreeItem *root = joint_tree->create_item(); + + Map<int, TreeItem *> items; + + items.insert(-1, root); + + const Vector<int> &joint_porder = skeleton->get_bone_process_orders(); + + Ref<Texture> bone_icon = get_theme_icon("Skeleton3D", "EditorIcons"); + + for (int i = 0; i < joint_porder.size(); ++i) { + const int b_idx = joint_porder[i]; + + const int p_idx = skeleton->get_bone_parent(b_idx); + TreeItem *p_item = items.find(p_idx)->get(); + + TreeItem *joint_item = joint_tree->create_item(p_item); + items.insert(b_idx, joint_item); + + joint_item->set_text(0, skeleton->get_bone_name(b_idx)); + joint_item->set_icon(0, bone_icon); + joint_item->set_selectable(0, true); + joint_item->set_metadata(0, "bones/" + itos(b_idx)); + } +} + +void Skeleton3DEditor::update_editors() { } -Skeleton3DEditor::Skeleton3DEditor() { - skeleton = nullptr; +void Skeleton3DEditor::create_editors() { + set_h_size_flags(SIZE_EXPAND_FILL); + add_theme_constant_override("separation", 0); + + set_focus_mode(FOCUS_ALL); + + // Create Top Menu Bar options = memnew(MenuButton); Node3DEditor::get_singleton()->add_control_to_menu_panel(options); @@ -156,31 +666,119 @@ Skeleton3DEditor::Skeleton3DEditor() { options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_option)); options->hide(); + + const Color section_color = get_theme_color("prop_subsection", "Editor"); + + EditorInspectorSection *bones_section = memnew(EditorInspectorSection); + bones_section->setup("bones", "Bones", skeleton, section_color, true); + add_child(bones_section); + bones_section->unfold(); + + ScrollContainer *s_con = memnew(ScrollContainer); + s_con->set_h_size_flags(SIZE_EXPAND_FILL); + s_con->set_custom_minimum_size(Size2(1, 350) * EDSCALE); + bones_section->get_vbox()->add_child(s_con); + + joint_tree = memnew(Tree); + joint_tree->set_columns(1); + joint_tree->set_focus_mode(Control::FocusMode::FOCUS_NONE); + joint_tree->set_select_mode(Tree::SELECT_SINGLE); + joint_tree->set_hide_root(true); + joint_tree->set_v_size_flags(SIZE_EXPAND_FILL); + joint_tree->set_h_size_flags(SIZE_EXPAND_FILL); + joint_tree->set_allow_rmb_select(true); + joint_tree->set_drag_forwarding(this); + s_con->add_child(joint_tree); + + pose_editor = memnew(BoneTransformEditor(skeleton)); + pose_editor->set_label(TTR("Bone Pose")); + pose_editor->set_keyable(AnimationPlayerEditor::singleton->get_track_editor()->has_keying()); + pose_editor->set_toggle_enabled(true); + pose_editor->set_visible(false); + add_child(pose_editor); + + rest_editor = memnew(BoneTransformEditor(skeleton)); + rest_editor->set_label(TTR("Bone Rest")); + rest_editor->set_visible(false); + add_child(rest_editor); + + custom_pose_editor = memnew(BoneTransformEditor(skeleton)); + custom_pose_editor->set_label(TTR("Bone Custom Pose")); + custom_pose_editor->set_visible(false); + add_child(custom_pose_editor); +} + +void Skeleton3DEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + create_editors(); + update_joint_tree(); + update_editors(); + + get_tree()->connect_compat("node_removed", this, "_node_removed", Vector<Variant>(), Object::CONNECT_ONESHOT); + joint_tree->connect_compat("item_selected", this, "_joint_tree_selection_changed"); + joint_tree->connect_compat("item_rmb_selected", this, "_joint_tree_rmb_select"); +#ifdef TOOLS_ENABLED + skeleton->connect_compat("pose_updated", this, "_update_properties"); +#endif // TOOLS_ENABLED + + break; + } + } } -Skeleton3DEditor::~Skeleton3DEditor() {} +void Skeleton3DEditor::_node_removed(Node *p_node) { + if (skeleton && p_node == skeleton) { + skeleton = nullptr; + options->hide(); + } -void Skeleton3DEditorPlugin::edit(Object *p_object) { - skeleton_editor->edit(Object::cast_to<Skeleton3D>(p_object)); + _update_properties(); } -bool Skeleton3DEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("Skeleton3D"); +void Skeleton3DEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_node_removed"), &Skeleton3DEditor::_node_removed); + ClassDB::bind_method(D_METHOD("_joint_tree_selection_changed"), &Skeleton3DEditor::_joint_tree_selection_changed); + ClassDB::bind_method(D_METHOD("_joint_tree_rmb_select"), &Skeleton3DEditor::_joint_tree_rmb_select); + ClassDB::bind_method(D_METHOD("_update_properties"), &Skeleton3DEditor::_update_properties); + ClassDB::bind_method(D_METHOD("_on_click_option"), &Skeleton3DEditor::_on_click_option); + + ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &Skeleton3DEditor::get_drag_data_fw); + ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &Skeleton3DEditor::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("drop_data_fw"), &Skeleton3DEditor::drop_data_fw); + ClassDB::bind_method(D_METHOD("move_skeleton_bone"), &Skeleton3DEditor::move_skeleton_bone); +} + +Skeleton3DEditor::Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton3D *p_skeleton) : + editor(p_editor), + editor_plugin(e_plugin), + skeleton(p_skeleton) { } -void Skeleton3DEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - skeleton_editor->options->show(); - } else { - skeleton_editor->options->hide(); - skeleton_editor->edit(nullptr); +Skeleton3DEditor::~Skeleton3DEditor() { + if (options) { + Node3DEditor::get_singleton()->remove_control_from_menu_panel(options); } } +bool EditorInspectorPluginSkeleton::can_handle(Object *p_object) { + return Object::cast_to<Skeleton3D>(p_object) != nullptr; +} + +void EditorInspectorPluginSkeleton::parse_begin(Object *p_object) { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_object); + ERR_FAIL_COND(!skeleton); + + Skeleton3DEditor *skel_editor = memnew(Skeleton3DEditor(this, editor, skeleton)); + add_custom_control(skel_editor); +} + Skeleton3DEditorPlugin::Skeleton3DEditorPlugin(EditorNode *p_node) { editor = p_node; - skeleton_editor = memnew(Skeleton3DEditor); - editor->get_viewport()->add_child(skeleton_editor); -} -Skeleton3DEditorPlugin::~Skeleton3DEditorPlugin() {} + Ref<EditorInspectorPluginSkeleton> skeleton_plugin; + skeleton_plugin.instance(); + skeleton_plugin->editor = editor; + + EditorInspector::add_inspector_plugin(skeleton_plugin); +} diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h index af9ebb6246..8b0639ed92 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.h +++ b/editor/plugins/skeleton_3d_editor_plugin.h @@ -35,11 +35,97 @@ #include "editor/editor_plugin.h" #include "scene/3d/skeleton_3d.h" +class EditorInspectorPluginSkeleton; +class Joint; class PhysicalBone3D; -class Joint3D; +class Skeleton3DEditorPlugin; +class Button; +class CheckBox; -class Skeleton3DEditor : public Node { - GDCLASS(Skeleton3DEditor, Node); +class BoneTransformEditor : public VBoxContainer { + GDCLASS(BoneTransformEditor, VBoxContainer); + + static const int32_t TRANSLATION_COMPONENTS = 3; + static const int32_t ROTATION_DEGREES_COMPONENTS = 3; + static const int32_t SCALE_COMPONENTS = 3; + static const int32_t BASIS_COMPONENTS = 9; + static const int32_t BASIS_SPLIT_COMPONENTS = 3; + static const int32_t TRANSFORM_COMPONENTS = 12; + static const int32_t TRANSFORM_SPLIT_COMPONENTS = 3; + static const int32_t TRANSFORM_CONTROL_COMPONENTS = 3; + + EditorInspectorSection *section; + + GridContainer *translation_grid; + GridContainer *rotation_grid; + GridContainer *scale_grid; + GridContainer *transform_grid; + + EditorSpinSlider *translation_slider[TRANSLATION_COMPONENTS]; + EditorSpinSlider *rotation_slider[ROTATION_DEGREES_COMPONENTS]; + EditorSpinSlider *scale_slider[SCALE_COMPONENTS]; + EditorSpinSlider *transform_slider[TRANSFORM_COMPONENTS]; + + Rect2 background_rects[5]; + + Skeleton3D *skeleton; + String property; + + UndoRedo *undo_redo; + + Button *key_button; + CheckBox *enabled_checkbox; + + bool keyable; + bool toggle_enabled; + bool updating; + + String label; + + void create_editors(); + void setup_spinner(EditorSpinSlider *spinner, const bool is_transform_spinner); + + void _value_changed(const double p_value, const bool p_from_transform); + + Transform compute_transform(const bool p_from_transform) const; + + void update_enabled_checkbox(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + BoneTransformEditor(Skeleton3D *p_skeleton); + + // Which transform target to modify + void set_target(const String &p_prop); + void set_label(const String &p_label) { label = p_label; } + + void _update_properties(); + void _update_custom_pose_properties(); + void _update_transform_properties(Transform p_transform); + + // Can/cannot modify the spinner values for the Transform + void set_read_only(const bool p_read_only); + + // Transform can be keyed, whether or not to show the button + void set_keyable(const bool p_keyable); + + // Bone can be toggled enabled or disabled, whether or not to show the checkbox + void set_toggle_enabled(const bool p_enabled); + + // Key Transform Button pressed + void _key_button_pressed(); + + // Bone Enabled Checkbox toggled + void _checkbox_toggled(const bool p_toggled); +}; + +class Skeleton3DEditor : public VBoxContainer { + GDCLASS(Skeleton3DEditor, VBoxContainer); + + friend class Skeleton3DEditorPlugin; enum Menu { MENU_OPTION_CREATE_PHYSICAL_SKELETON @@ -51,44 +137,78 @@ class Skeleton3DEditor : public Node { BoneInfo() {} }; + EditorNode *editor; + EditorInspectorPluginSkeleton *editor_plugin; + Skeleton3D *skeleton; + Tree *joint_tree; + BoneTransformEditor *rest_editor; + BoneTransformEditor *pose_editor; + BoneTransformEditor *custom_pose_editor; + MenuButton *options; + EditorFileDialog *file_dialog; + + UndoRedo *undo_redo; void _on_click_option(int p_option); + void _file_selected(const String &p_file); - friend class Skeleton3DEditorPlugin; + EditorFileDialog *file_export_lib; + + void update_joint_tree(); + void update_editors(); + + void create_editors(); + + void create_physical_skeleton(); + PhysicalBone3D *create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos); + + Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); + bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; + void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); protected: void _notification(int p_what); void _node_removed(Node *p_node); static void _bind_methods(); - void create_physical_skeleton(); - PhysicalBone3D *create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos); - public: - void edit(Skeleton3D *p_node); + void move_skeleton_bone(NodePath p_skeleton_path, int32_t p_selected_boneidx, int32_t p_target_boneidx); - Skeleton3DEditor(); + Skeleton3D *get_skeleton() const { return skeleton; }; + + void _joint_tree_selection_changed(); + void _joint_tree_rmb_select(const Vector2 &p_pos); + + void _update_properties(); + + Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton3D *skeleton); ~Skeleton3DEditor(); }; +class EditorInspectorPluginSkeleton : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginSkeleton, EditorInspectorPlugin); + + friend class Skeleton3DEditorPlugin; + + EditorNode *editor; + +public: + virtual bool can_handle(Object *p_object); + virtual void parse_begin(Object *p_object); +}; + class Skeleton3DEditorPlugin : public EditorPlugin { GDCLASS(Skeleton3DEditorPlugin, EditorPlugin); EditorNode *editor; - Skeleton3DEditor *skeleton_editor; public: - virtual String get_name() const { return "Skeleton3D"; } - virtual bool has_main_screen() const { return false; } - virtual void edit(Object *p_object); - virtual bool handles(Object *p_object) const; - virtual void make_visible(bool p_visible); - Skeleton3DEditorPlugin(EditorNode *p_node); - ~Skeleton3DEditorPlugin(); + + virtual String get_name() const { return "Skeleton3D"; } }; #endif // SKELETON_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/sprite_2d_editor_plugin.cpp b/editor/plugins/sprite_2d_editor_plugin.cpp index b21586a6b0..f5fafb68a5 100644 --- a/editor/plugins/sprite_2d_editor_plugin.cpp +++ b/editor/plugins/sprite_2d_editor_plugin.cpp @@ -31,6 +31,7 @@ #include "sprite_2d_editor_plugin.h" #include "canvas_item_editor_plugin.h" +#include "core/math/geometry_2d.h" #include "editor/editor_scale.h" #include "scene/2d/collision_polygon_2d.h" #include "scene/2d/light_occluder_2d.h" @@ -233,7 +234,7 @@ void Sprite2DEditor::_update_mesh_data() { computed_vertices.push_back(vtx); } - Vector<int> poly = Geometry::triangulate_polygon(lines[j]); + Vector<int> poly = Geometry2D::triangulate_polygon(lines[j]); for (int i = 0; i < poly.size(); i += 3) { for (int k = 0; k < 3; k++) { diff --git a/editor/plugins/tile_map_editor_plugin.cpp b/editor/plugins/tile_map_editor_plugin.cpp index 3010d72d81..3281a59c1c 100644 --- a/editor/plugins/tile_map_editor_plugin.cpp +++ b/editor/plugins/tile_map_editor_plugin.cpp @@ -194,6 +194,21 @@ void TileMapEditor::_palette_multi_selected(int index, bool selected) { _update_palette(); } +void TileMapEditor::_palette_input(const Ref<InputEvent> &p_event) { + const Ref<InputEventMouseButton> mb = p_event; + + // Zoom in/out using Ctrl + mouse wheel. + if (mb.is_valid() && mb->is_pressed() && mb->get_command()) { + if (mb->is_pressed() && mb->get_button_index() == BUTTON_WHEEL_UP) { + size_slider->set_value(size_slider->get_value() + 0.2); + } + + if (mb->is_pressed() && mb->get_button_index() == BUTTON_WHEEL_DOWN) { + size_slider->set_value(size_slider->get_value() - 0.2); + } + } +} + void TileMapEditor::_canvas_mouse_enter() { mouse_over = true; CanvasItemEditor::get_singleton()->update_viewport(); @@ -1913,6 +1928,7 @@ TileMapEditor::TileMapEditor(EditorNode *p_editor) { palette->add_theme_constant_override("vseparation", 8 * EDSCALE); palette->connect("item_selected", callable_mp(this, &TileMapEditor::_palette_selected)); palette->connect("multi_selected", callable_mp(this, &TileMapEditor::_palette_multi_selected)); + palette->connect("gui_input", callable_mp(this, &TileMapEditor::_palette_input)); palette_container->add_child(palette); // Add message for when no texture is selected. @@ -1951,7 +1967,7 @@ TileMapEditor::TileMapEditor(EditorNode *p_editor) { toolbar->add_child(paint_button); bucket_fill_button = memnew(ToolButton); - bucket_fill_button->set_shortcut(ED_SHORTCUT("tile_map_editor/bucket_fill", TTR("Bucket Fill"), KEY_G)); + bucket_fill_button->set_shortcut(ED_SHORTCUT("tile_map_editor/bucket_fill", TTR("Bucket Fill"), KEY_B)); bucket_fill_button->connect("pressed", callable_mp(this, &TileMapEditor::_button_tool_select), make_binds(TOOL_BUCKET)); bucket_fill_button->set_toggle_mode(true); toolbar->add_child(bucket_fill_button); diff --git a/editor/plugins/tile_map_editor_plugin.h b/editor/plugins/tile_map_editor_plugin.h index 5f82d7bfb8..e25e2d2add 100644 --- a/editor/plugins/tile_map_editor_plugin.h +++ b/editor/plugins/tile_map_editor_plugin.h @@ -182,6 +182,7 @@ class TileMapEditor : public VBoxContainer { void _menu_option(int p_option); void _palette_selected(int index); void _palette_multi_selected(int index, bool selected); + void _palette_input(const Ref<InputEvent> &p_event); Dictionary _create_cell_dictionary(int tile, bool flip_x, bool flip_y, bool transpose, Vector2 autotile_coord); void _start_undo(const String &p_action); diff --git a/editor/plugins/tile_set_editor_plugin.cpp b/editor/plugins/tile_set_editor_plugin.cpp index 644facd5bd..b3d4b391d3 100644 --- a/editor/plugins/tile_set_editor_plugin.cpp +++ b/editor/plugins/tile_set_editor_plugin.cpp @@ -569,6 +569,7 @@ TileSetEditor::TileSetEditor(EditorNode *p_editor) { scroll = memnew(ScrollContainer); main_vb->add_child(scroll); scroll->set_v_size_flags(SIZE_EXPAND_FILL); + scroll->connect("gui_input", callable_mp(this, &TileSetEditor::_on_scroll_container_input)); scroll->set_clip_contents(true); empty_message = memnew(Label); @@ -1198,6 +1199,27 @@ bool TileSetEditor::is_within_grabbing_distance_of_first_point(const Vector2 &p_ return distance < p_grab_threshold; } +void TileSetEditor::_on_scroll_container_input(const Ref<InputEvent> &p_event) { + const Ref<InputEventMouseButton> mb = p_event; + + if (mb.is_valid()) { + // Zoom in/out using Ctrl + mouse wheel. This is done on the ScrollContainer + // to allow performing this action anywhere, even if the cursor isn't + // hovering the texture in the workspace. + if (mb->get_button_index() == BUTTON_WHEEL_UP && mb->is_pressed() && mb->get_control()) { + print_line("zooming in"); + _zoom_in(); + // Don't scroll up after zooming in. + accept_event(); + } else if (mb->get_button_index() == BUTTON_WHEEL_DOWN && mb->is_pressed() && mb->get_control()) { + print_line("zooming out"); + _zoom_out(); + // Don't scroll down after zooming out. + accept_event(); + } + } +} + void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) { if (tileset.is_null() || !get_current_texture().is_valid()) { return; @@ -1214,8 +1236,8 @@ void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) { } current_tile_region.position += WORKSPACE_MARGIN; - Ref<InputEventMouseButton> mb = p_ie; - Ref<InputEventMouseMotion> mm = p_ie; + const Ref<InputEventMouseButton> mb = p_ie; + const Ref<InputEventMouseMotion> mm = p_ie; if (mb.is_valid()) { if (mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT && !creating_shape) { @@ -1239,13 +1261,6 @@ void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) { delete tiles; } } - - // Mouse Wheel Event - if (mb->get_button_index() == BUTTON_WHEEL_UP && mb->is_pressed() && mb->get_control()) { - _zoom_in(); - } else if (mb->get_button_index() == BUTTON_WHEEL_DOWN && mb->is_pressed() && mb->get_control()) { - _zoom_out(); - } } // Drag Middle Mouse if (mm.is_valid()) { diff --git a/editor/plugins/tile_set_editor_plugin.h b/editor/plugins/tile_set_editor_plugin.h index 827325cfd7..2955dda244 100644 --- a/editor/plugins/tile_set_editor_plugin.h +++ b/editor/plugins/tile_set_editor_plugin.h @@ -200,6 +200,7 @@ private: void _on_workspace_overlay_draw(); void _on_workspace_draw(); void _on_workspace_process(); + void _on_scroll_container_input(const Ref<InputEvent> &p_event); void _on_workspace_input(const Ref<InputEvent> &p_ie); void _on_tool_clicked(int p_tool); void _on_priority_changed(float val); diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index dc5ff6a5eb..499f7d4017 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -916,6 +916,8 @@ public: icon = nullptr; icon_needs_reload = true; hover = false; + + set_focus_mode(FocusMode::FOCUS_ALL); } void set_is_favorite(bool fav) { @@ -1739,6 +1741,10 @@ void ProjectList::_panel_input(const Ref<InputEvent> &p_ev, Node *p_hb) { select_project(clicked_index); } + if (_selected_project_keys.has(clicked_project.project_key)) { + clicked_project.control->grab_focus(); + } + emit_signal(SIGNAL_SELECTION_CHANGED); if (!mb->get_control() && mb->is_doubleclick()) { diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp index 6888ebdc71..c6c93fae83 100644 --- a/editor/property_selector.cpp +++ b/editor/property_selector.cpp @@ -357,7 +357,7 @@ void PropertySelector::_item_selected() { if (E) { for (int i = 0; i < E->get().properties.size(); i++) { if (E->get().properties[i].name == name) { - text = E->get().properties[i].description; + text = DTR(E->get().properties[i].description); } } } @@ -372,7 +372,7 @@ void PropertySelector::_item_selected() { if (E) { for (int i = 0; i < E->get().methods.size(); i++) { if (E->get().methods[i].name == name) { - text = E->get().methods[i].description; + text = DTR(E->get().methods[i].description); } } } diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index a81a2ff4e9..c37d32b26b 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -2557,6 +2557,11 @@ void SceneTreeDock::_focus_node() { } void SceneTreeDock::attach_script_to_selected(bool p_extend) { + if (ScriptServer::get_language_count() == 0) { + EditorNode::get_singleton()->show_warning(TTR("Cannot attach a script: there are no languages registered.\nThis is probably because this editor was built with all language modules disabled.")); + return; + } + if (!profile_allow_script_editing) { return; } diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp index 04fbfdff9d..ae5229b628 100644 --- a/editor/script_create_dialog.cpp +++ b/editor/script_create_dialog.cpp @@ -783,7 +783,7 @@ ScriptCreateDialog::ScriptCreateDialog() { gc->add_child(memnew(Label(TTR("Language:")))); gc->add_child(language_menu); - default_language = 0; + default_language = -1; for (int i = 0; i < ScriptServer::get_language_count(); i++) { String lang = ScriptServer::get_language(i)->get_name(); language_menu->add_item(lang); @@ -791,8 +791,9 @@ ScriptCreateDialog::ScriptCreateDialog() { default_language = i; } } - - language_menu->select(default_language); + if (default_language >= 0) { + language_menu->select(default_language); + } current_language = default_language; language_menu->connect("item_selected", callable_mp(this, &ScriptCreateDialog::_lang_changed)); |