diff options
Diffstat (limited to 'editor')
43 files changed, 1776 insertions, 676 deletions
diff --git a/editor/action_map_editor.cpp b/editor/action_map_editor.cpp index 38db48a4d4..363d542fd5 100644 --- a/editor/action_map_editor.cpp +++ b/editor/action_map_editor.cpp @@ -126,28 +126,30 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event) { while (category) { TreeItem *input_item = category->get_first_child(); - // has_type this should be always true, unless the tree structure has been misconfigured. - bool has_type = input_item->get_parent()->has_meta("__type"); - int input_type = input_item->get_parent()->get_meta("__type"); - if (!has_type) { - return; - } + if (input_item != nullptr) { + // has_type this should be always true, unless the tree structure has been misconfigured. + bool has_type = input_item->get_parent()->has_meta("__type"); + int input_type = input_item->get_parent()->get_meta("__type"); + if (!has_type) { + return; + } - // If event type matches input types of this category. - if ((k.is_valid() && input_type == INPUT_KEY) || (joyb.is_valid() && input_type == INPUT_JOY_BUTTON) || (joym.is_valid() && input_type == INPUT_JOY_MOTION) || (mb.is_valid() && input_type == INPUT_MOUSE_BUTTON)) { - // Loop through all items of this category until one matches. - while (input_item) { - bool key_match = k.is_valid() && (Variant(k->get_keycode()) == input_item->get_meta("__keycode") || Variant(k->get_physical_keycode()) == input_item->get_meta("__keycode")); - bool joyb_match = joyb.is_valid() && Variant(joyb->get_button_index()) == input_item->get_meta("__index"); - bool joym_match = joym.is_valid() && Variant(joym->get_axis()) == input_item->get_meta("__axis") && joym->get_axis_value() == (float)input_item->get_meta("__value"); - bool mb_match = mb.is_valid() && Variant(mb->get_button_index()) == input_item->get_meta("__index"); - if (key_match || joyb_match || joym_match || mb_match) { - category->set_collapsed(false); - input_item->select(0); - input_list_tree->ensure_cursor_is_visible(); - return; + // If event type matches input types of this category. + if ((k.is_valid() && input_type == INPUT_KEY) || (joyb.is_valid() && input_type == INPUT_JOY_BUTTON) || (joym.is_valid() && input_type == INPUT_JOY_MOTION) || (mb.is_valid() && input_type == INPUT_MOUSE_BUTTON)) { + // Loop through all items of this category until one matches. + while (input_item) { + bool key_match = k.is_valid() && (Variant(k->get_keycode()) == input_item->get_meta("__keycode") || Variant(k->get_physical_keycode()) == input_item->get_meta("__keycode")); + bool joyb_match = joyb.is_valid() && Variant(joyb->get_button_index()) == input_item->get_meta("__index"); + bool joym_match = joym.is_valid() && Variant(joym->get_axis()) == input_item->get_meta("__axis") && joym->get_axis_value() == (float)input_item->get_meta("__value"); + bool mb_match = mb.is_valid() && Variant(mb->get_button_index()) == input_item->get_meta("__index"); + if (key_match || joyb_match || joym_match || mb_match) { + category->set_collapsed(false); + input_item->select(0); + input_list_tree->ensure_cursor_is_visible(); + return; + } + input_item = input_item->get_next(); } - input_item = input_item->get_next(); } } diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index d5afd5020c..14ca35a664 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -541,7 +541,7 @@ public: String hint_string; if (v.get_type() == Variant::OBJECT) { - //could actually check the object property if exists..? yes i will! + // Could actually check the object property if exists..? Yes I will! Ref<Resource> res = v; if (res.is_valid()) { hint = PROPERTY_HINT_RESOURCE_TYPE; @@ -1177,7 +1177,7 @@ public: String hint_string; if (v.get_type() == Variant::OBJECT) { - //could actually check the object property if exists..? yes i will! + // Could actually check the object property if exists..? Yes I will! Ref<Resource> res = v; if (res.is_valid()) { hint = PROPERTY_HINT_RESOURCE_TYPE; @@ -1388,7 +1388,7 @@ void AnimationTimelineEdit::_notification(int p_what) { float l = animation->get_length(); if (l <= 0) { - l = 0.001; //avoid crashor + l = 0.001; // Avoid crashor. } Ref<Texture2D> hsize_icon = get_theme_icon(SNAME("Hsize"), SNAME("EditorIcons")); @@ -1401,18 +1401,12 @@ void AnimationTimelineEdit::_notification(int p_what) { for (int i = 0; i < animation->get_track_count(); i++) { if (animation->track_get_key_count(i) > 0) { float beg = animation->track_get_key_time(i, 0); - /*if (animation->track_get_type(i) == Animation::TYPE_BEZIER) { - beg += animation->bezier_track_get_key_in_handle(i, 0).x; - }* not worth it since they have no use */ if (beg < time_min) { time_min = beg; } float end = animation->track_get_key_time(i, animation->track_get_key_count(i) - 1); - /*if (animation->track_get_type(i) == Animation::TYPE_BEZIER) { - end += animation->bezier_track_get_key_out_handle(i, animation->track_get_key_count(i) - 1).x; - } not worth it since they have no use */ if (end > time_max) { time_max = end; @@ -1422,8 +1416,6 @@ void AnimationTimelineEdit::_notification(int p_what) { float extra = (zoomw / scale) * 0.5; - //if (time_min < -0.001) - // time_min -= extra; time_max += extra; set_min(time_min); set_max(time_max); @@ -1853,7 +1845,7 @@ void AnimationTrackEdit::_notification(int p_what) { { Ref<Texture2D> check = animation->track_is_enabled(track) ? get_theme_icon(SNAME("checked"), SNAME("CheckBox")) : get_theme_icon(SNAME("unchecked"), SNAME("CheckBox")); - int ofs = in_group ? check->get_width() : 0; //not the best reference for margin but.. + int ofs = in_group ? check->get_width() : 0; // Not the best reference for margin but.. check_rect = Rect2(Point2(ofs, int(get_size().height - check->get_height()) / 2), check->get_size()); draw_texture(check, check_rect.position); @@ -1971,7 +1963,7 @@ void AnimationTrackEdit::_notification(int p_what) { ofs += hsep; { - //callmode + // Callmode. Animation::UpdateMode update_mode; @@ -1990,7 +1982,7 @@ void AnimationTrackEdit::_notification(int p_what) { if (animation->track_get_type(track) == Animation::TYPE_VALUE) { draw_texture(update_icon, update_mode_rect.position); } - //make it easier to click + // Make it easier to click. update_mode_rect.position.y = 0; update_mode_rect.size.y = get_size().height; @@ -2019,7 +2011,7 @@ void AnimationTrackEdit::_notification(int p_what) { } { - //interp + // Interp. Animation::InterpolationType interp_mode = animation->track_get_interpolation_type(track); @@ -2032,7 +2024,7 @@ void AnimationTrackEdit::_notification(int p_what) { if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM3D) { draw_texture(icon, interp_mode_rect.position); } - //make it easier to click + // Make it easier to click. interp_mode_rect.position.y = 0; interp_mode_rect.size.y = get_size().height; @@ -2052,7 +2044,7 @@ void AnimationTrackEdit::_notification(int p_what) { } { - //loop + // Loop. bool loop_wrap = animation->track_get_interpolation_loop_wrap(track); @@ -2085,7 +2077,7 @@ void AnimationTrackEdit::_notification(int p_what) { } { - //erase + // Erase. Ref<Texture2D> icon = get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")); @@ -2132,7 +2124,7 @@ Rect2 AnimationTrackEdit::get_key_rect(int p_index, float p_pixels_sec) { } Rect2 rect = Rect2(-type_icon->get_width() / 2, 0, type_icon->get_width(), get_size().height); - //make it a big easier to click + // Make it a big easier to click. rect.position.x -= rect.size.x * 0.5; rect.size.x *= 2; return rect; @@ -2221,7 +2213,7 @@ void AnimationTrackEdit::draw_key(int p_index, float p_pixels_sec, int p_x, bool draw_texture(icon_to_draw, ofs); } -//helper +// Helper. void AnimationTrackEdit::draw_rect_clipped(const Rect2 &p_rect, const Color &p_color, bool p_filled) { int clip_left = timeline->get_name_limit(); int clip_right = get_size().width - timeline->get_buttons_width(); @@ -2246,7 +2238,7 @@ void AnimationTrackEdit::draw_texture_region_clipped(const Ref<Texture2D> &p_tex int clip_left = timeline->get_name_limit(); int clip_right = get_size().width - timeline->get_buttons_width(); - //clip left and right + // Clip left and right. if (clip_left > p_rect.position.x + p_rect.size.x) { return; } @@ -2455,7 +2447,7 @@ String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const { key_distance = distance; } } else { - //first one does it + // First one does it. break; } } @@ -3001,7 +2993,7 @@ AnimationTrackEdit::AnimationTrackEdit() { play_position->set_anchors_and_offsets_preset(PRESET_WIDE); play_position->connect("draw", callable_mp(this, &AnimationTrackEdit::_play_position_draw)); set_focus_mode(FOCUS_CLICK); - set_mouse_filter(MOUSE_FILTER_PASS); //scroll has to work too for selection + set_mouse_filter(MOUSE_FILTER_PASS); // Scroll has to work too for selection. } ////////////////////////////////////// @@ -3338,30 +3330,16 @@ static bool track_type_is_resettable(Animation::TrackType p_type) { } } -void AnimationTrackEditor::_query_insert(const InsertData &p_id) { - if (insert_frame != Engine::get_singleton()->get_frames_drawn()) { - //clear insert list for the frame if frame changed - if (insert_confirm->is_visible()) { - return; //do nothing - } - insert_data.clear(); - insert_query = false; - } - insert_frame = Engine::get_singleton()->get_frames_drawn(); - - for (const InsertData &E : insert_data) { - //prevent insertion of multiple tracks - if (E.path == p_id.path) { - return; //already inserted a track for this on this frame - } - } - - insert_data.push_back(p_id); +void AnimationTrackEditor::make_insert_queue() { + insert_data.clear(); + insert_queue = true; +} +void AnimationTrackEditor::commit_insert_queue() { bool reset_allowed = true; - AnimationPlayer *player = AnimationPlayerEditor::singleton->get_player(); + AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player(); if (player->has_animation("RESET") && player->get_animation("RESET") == animation) { - // Avoid messing with the reset animation itself + // Avoid messing with the reset animation itself. reset_allowed = false; } else { bool some_resettable = false; @@ -3376,74 +3354,82 @@ void AnimationTrackEditor::_query_insert(const InsertData &p_id) { } } - if (p_id.track_idx == -1) { - int num_tracks = 0; - bool all_bezier = true; - for (int i = 0; i < insert_data.size(); i++) { - if (insert_data[i].type != Animation::TYPE_VALUE && insert_data[i].type != Animation::TYPE_BEZIER) { - all_bezier = false; - } - - if (insert_data[i].track_idx == -1) { - ++num_tracks; - } + // Organize insert data. + int num_tracks = 0; + String last_track_query; + bool all_bezier = true; + for (int i = 0; i < insert_data.size(); i++) { + if (insert_data[i].type != Animation::TYPE_VALUE && insert_data[i].type != Animation::TYPE_BEZIER) { + all_bezier = false; + } - if (insert_data[i].type != Animation::TYPE_VALUE) { - continue; - } + if (insert_data[i].track_idx == -1) { + ++num_tracks; + last_track_query = insert_data[i].query; + } - switch (insert_data[i].value.get_type()) { - case Variant::INT: - case Variant::FLOAT: - case Variant::VECTOR2: - case Variant::VECTOR3: - case Variant::QUATERNION: - case Variant::PLANE: - case Variant::COLOR: { - // Valid. - } break; - default: { - all_bezier = false; - } - } + if (insert_data[i].type != Animation::TYPE_VALUE) { + continue; } - if (bool(EDITOR_DEF("editors/animation/confirm_insert_track", true))) { - //potential new key, does not exist - if (num_tracks == 1) { - // TRANSLATORS: %s will be replaced by a phrase describing the target of track. - insert_confirm_text->set_text(vformat(TTR("Create new track for %s and insert key?"), p_id.query)); - } else { - insert_confirm_text->set_text(vformat(TTR("Create %d new tracks and insert keys?"), num_tracks)); + switch (insert_data[i].value.get_type()) { + case Variant::INT: + case Variant::FLOAT: + case Variant::VECTOR2: + case Variant::VECTOR3: + case Variant::QUATERNION: + case Variant::PLANE: + case Variant::COLOR: { + // Valid. + } break; + default: { + all_bezier = false; } + } + } - insert_confirm_bezier->set_visible(all_bezier); - insert_confirm_reset->set_visible(reset_allowed); - - insert_confirm->get_ok_button()->set_text(TTR("Create")); - insert_confirm->popup_centered(); - insert_query = true; + if (bool(EDITOR_DEF("editors/animation/confirm_insert_track", true)) && num_tracks > 0) { + // Potentially a new key, does not exist. + if (num_tracks == 1) { + // TRANSLATORS: %s will be replaced by a phrase describing the target of track. + insert_confirm_text->set_text(vformat(TTR("Create new track for %s and insert key?"), last_track_query)); } else { - call_deferred(SNAME("_insert_delay"), reset_allowed && EDITOR_GET("editors/animation/default_create_reset_tracks"), all_bezier && EDITOR_GET("editors/animation/default_create_bezier_tracks")); - insert_queue = true; + insert_confirm_text->set_text(vformat(TTR("Create %d new tracks and insert keys?"), num_tracks)); } + insert_confirm_bezier->set_visible(all_bezier); + insert_confirm_reset->set_visible(reset_allowed); + + insert_confirm->get_ok_button()->set_text(TTR("Create")); + insert_confirm->popup_centered(); } else { - if (!insert_query && !insert_queue) { - // Create Beziers wouldn't make sense in this case, where no tracks are being created - call_deferred(SNAME("_insert_delay"), reset_allowed && EDITOR_GET("editors/animation/default_create_reset_tracks"), false); - insert_queue = true; - } + _insert_track(reset_allowed && EDITOR_GET("editors/animation/default_create_reset_tracks"), all_bezier && EDITOR_GET("editors/animation/default_create_bezier_tracks")); } + + insert_queue = false; } -void AnimationTrackEditor::_insert_delay(bool p_create_reset, bool p_create_beziers) { - if (insert_query) { - //discard since it's entered into query mode - insert_queue = false; - return; +void AnimationTrackEditor::_query_insert(const InsertData &p_id) { + if (!insert_queue) { + insert_data.clear(); + } + + for (const InsertData &E : insert_data) { + // Prevent insertion of multiple tracks. + if (E.path == p_id.path) { + return; // Already inserted a track this frame. + } + } + + insert_data.push_back(p_id); + + // Without queue, commit immediately. + if (!insert_queue) { + commit_insert_queue(); } +} +void AnimationTrackEditor::_insert_track(bool p_create_reset, bool p_create_beziers) { undo_redo->create_action(TTR("Anim Insert")); Ref<Animation> reset_anim; @@ -3478,7 +3464,6 @@ void AnimationTrackEditor::_insert_delay(bool p_create_reset, bool p_create_bezi set_anim_pos(pos); emit_signal(SNAME("timeline_changed"), pos, true); } - insert_queue = false; } void AnimationTrackEditor::insert_transform_key(Node3D *p_node, const String &p_sub, const Transform3D &p_xform) { @@ -3490,7 +3475,7 @@ void AnimationTrackEditor::insert_transform_key(Node3D *p_node, const String &p_ } ERR_FAIL_COND(!root); - //let's build a node path + // Let's build a node path. String path = root->get_path_to(p_node); if (p_sub != "") { path += ":" + p_sub; @@ -3523,20 +3508,44 @@ void AnimationTrackEditor::insert_transform_key(Node3D *p_node, const String &p_ id.query = vformat(TTR("node '%s'"), p_node->get_name()); id.advance = false; - //dialog insert - + // Dialog insert. _query_insert(id); } +bool AnimationTrackEditor::has_transform_track(Node3D *p_node, const String &p_sub) { + if (!keying) { + return false; + } + if (!animation.is_valid()) { + return false; + } + if (!root) { + return false; + } + + //let's build a node path + String path = root->get_path_to(p_node); + if (p_sub != "") { + path += ":" + p_sub; + } + int track_id = animation->find_track(path); + if (track_id >= 0) { + if (animation->track_get_type(track_id) == Animation::TYPE_TRANSFORM3D) { + return true; + } + } + return false; +} + void AnimationTrackEditor::_insert_animation_key(NodePath p_path, const Variant &p_value) { String path = p_path; - //animation property is a special case, always creates an animation track + // Animation property is a special case, always creates an animation track. for (int i = 0; i < animation->get_track_count(); i++) { String np = animation->track_get_path(i); if (path == np && animation->track_get_type(i) == Animation::TYPE_ANIMATION) { - //exists + // Exists. InsertData id; id.path = path; id.track_idx = i; @@ -3545,7 +3554,7 @@ void AnimationTrackEditor::_insert_animation_key(NodePath p_path, const Variant // TRANSLATORS: This describes the target of new animation track, will be inserted into another string. id.query = TTR("animation"); id.advance = false; - //dialog insert + // Dialog insert. _query_insert(id); return; } @@ -3558,20 +3567,20 @@ void AnimationTrackEditor::_insert_animation_key(NodePath p_path, const Variant id.type = Animation::TYPE_ANIMATION; id.query = TTR("animation"); id.advance = false; - //dialog insert + // Dialog insert. _query_insert(id); } void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists) { ERR_FAIL_COND(!root); - //let's build a node path + // Let's build a node path. Node *node = p_node; String path = root->get_path_to(node); if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") { - if (node == AnimationPlayerEditor::singleton->get_player()) { + if (node == AnimationPlayerEditor::get_singleton()->get_player()) { EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players.")); return; } @@ -3590,7 +3599,7 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p NodePath np = path; - //locate track + // Locate track. bool inserted = false; @@ -3608,14 +3617,14 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p // TRANSLATORS: This describes the target of new animation track, will be inserted into another string. id.query = vformat(TTR("property '%s'"), p_property); id.advance = false; - //dialog insert + // Dialog insert. _query_insert(id); inserted = true; } else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) { Variant value; String track_path = animation->track_get_path(i); if (track_path == np) { - value = p_value; //all good + value = p_value; // All good. } else { int sep = track_path.rfind(":"); if (sep != -1) { @@ -3638,7 +3647,7 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p id.type = Animation::TYPE_BEZIER; id.query = vformat(TTR("property '%s'"), p_property); id.advance = false; - //dialog insert + // Dialog insert. _query_insert(id); inserted = true; } @@ -3654,7 +3663,7 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p id.type = Animation::TYPE_VALUE; id.query = vformat(TTR("property '%s'"), p_property); id.advance = false; - //dialog insert + // Dialog insert. _query_insert(id); } @@ -3662,7 +3671,7 @@ void AnimationTrackEditor::insert_value_key(const String &p_property, const Vari EditorHistory *history = EditorNode::get_singleton()->get_editor_history(); ERR_FAIL_COND(!root); - //let's build a node path + // Let's build a node path. ERR_FAIL_COND(history->get_path_size() == 0); Object *obj = ObjectDB::get_instance(history->get_path_object(0)); ERR_FAIL_COND(!Object::cast_to<Node>(obj)); @@ -3672,7 +3681,7 @@ void AnimationTrackEditor::insert_value_key(const String &p_property, const Vari String path = root->get_path_to(node); if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") { - if (node == AnimationPlayerEditor::singleton->get_player()) { + if (node == AnimationPlayerEditor::get_singleton()->get_player()) { EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players.")); return; } @@ -3690,10 +3699,11 @@ void AnimationTrackEditor::insert_value_key(const String &p_property, const Vari NodePath np = path; - //locate track + // Locate track. bool inserted = false; + make_insert_queue(); for (int i = 0; i < animation->get_track_count(); i++) { if (animation->track_get_type(i) == Animation::TYPE_VALUE) { if (animation->track_get_path(i) != np) { @@ -3707,13 +3717,13 @@ void AnimationTrackEditor::insert_value_key(const String &p_property, const Vari id.type = Animation::TYPE_VALUE; id.query = vformat(TTR("property '%s'"), p_property); id.advance = p_advance; - //dialog insert + // Dialog insert. _query_insert(id); inserted = true; } else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) { Variant value; if (animation->track_get_path(i) == np) { - value = p_value; //all good + value = p_value; // All good. } else { String tpath = animation->track_get_path(i); int index = tpath.rfind(":"); @@ -3732,11 +3742,12 @@ void AnimationTrackEditor::insert_value_key(const String &p_property, const Vari id.type = Animation::TYPE_BEZIER; id.query = vformat(TTR("property '%s'"), p_property); id.advance = p_advance; - //dialog insert + // Dialog insert. _query_insert(id); inserted = true; } } + commit_insert_queue(); if (!inserted) { InsertData id; @@ -3746,13 +3757,13 @@ void AnimationTrackEditor::insert_value_key(const String &p_property, const Vari id.type = Animation::TYPE_VALUE; id.query = vformat(TTR("property '%s'"), p_property); id.advance = p_advance; - //dialog insert + // Dialog insert. _query_insert(id); } } Ref<Animation> AnimationTrackEditor::_create_and_get_reset_animation() { - AnimationPlayer *player = AnimationPlayerEditor::singleton->get_player(); + AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player(); if (player->has_animation("RESET")) { return player->get_animation("RESET"); } else { @@ -3760,9 +3771,9 @@ Ref<Animation> AnimationTrackEditor::_create_and_get_reset_animation() { reset_anim.instantiate(); reset_anim->set_length(ANIM_MIN_LENGTH); undo_redo->add_do_method(player, "add_animation", "RESET", reset_anim); - undo_redo->add_do_method(AnimationPlayerEditor::singleton, "_animation_player_changed", player); + undo_redo->add_do_method(AnimationPlayerEditor::get_singleton(), "_animation_player_changed", player); undo_redo->add_undo_method(player, "remove_animation", "RESET"); - undo_redo->add_undo_method(AnimationPlayerEditor::singleton, "_animation_player_changed", player); + undo_redo->add_undo_method(AnimationPlayerEditor::get_singleton(), "_animation_player_changed", player); return reset_anim; } } @@ -3929,7 +3940,7 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD animation->add_track(p_id.type); animation->track_set_path(animation->get_track_count() - 1, p_id.path); PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np); - animation->remove_track(animation->get_track_count() - 1); //hack + animation->remove_track(animation->get_track_count() - 1); // Hack. if (h.type == Variant::FLOAT || h.type == Variant::VECTOR2 || @@ -4078,7 +4089,7 @@ void AnimationTrackEditor::_update_tracks() { for (int i = 0; i < animation->get_track_count(); i++) { AnimationTrackEdit *track_edit = nullptr; - //find hint and info for plugin + // Find hint and info for plugin. if (use_filter) { NodePath path = animation->track_get_path(i); @@ -4086,10 +4097,10 @@ void AnimationTrackEditor::_update_tracks() { if (root && root->has_node(path)) { Node *node = root->get_node(path); if (!node) { - continue; // no node, no filter + continue; // No node, no filter. } if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) { - continue; //skip track due to not selected + continue; // Skip track due to not selected. } } } @@ -4151,7 +4162,7 @@ void AnimationTrackEditor::_update_tracks() { } if (track_edit == nullptr) { - //no valid plugin_found + // No valid plugin_found. track_edit = memnew(AnimationTrackEdit); } @@ -4226,11 +4237,11 @@ void AnimationTrackEditor::_update_tracks() { void AnimationTrackEditor::_animation_changed() { if (animation_changing_awaiting_update) { - return; //all will be updated, don't bother with anything + return; // All will be updated, don't bother with anything. } if (key_edit && key_edit->setting) { - //if editing a key, just update the edited track, makes refresh less costly + // If editing a key, just update the edited track, makes refresh less costly. if (key_edit->track < track_edits.size()) { if (animation->track_get_type(key_edit->track) == Animation::TYPE_BEZIER) { bezier_edit->update(); @@ -4284,7 +4295,7 @@ void AnimationTrackEditor::_animation_update() { } if (track_edits.size() == animation->get_track_count()) { - //check tracks are the same + // Check tracks are the same. for (int i = 0; i < track_edits.size(); i++) { if (track_edits[i]->get_path() != animation->track_get_path(i)) { @@ -4446,7 +4457,7 @@ void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) { return; } - if (node == AnimationPlayerEditor::singleton->get_player()) { + if (node == AnimationPlayerEditor::get_singleton()->get_player()) { EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players.")); return; } @@ -4478,12 +4489,12 @@ void AnimationTrackEditor::_new_track_property_selected(String p_name) { if (adding_track_type == Animation::TYPE_VALUE) { Animation::UpdateMode update_mode = Animation::UPDATE_DISCRETE; { - //hack + // Hack. NodePath np; animation->add_track(Animation::TYPE_VALUE); animation->track_set_path(animation->get_track_count() - 1, full_path); PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np); - animation->remove_track(animation->get_track_count() - 1); //hack + animation->remove_track(animation->get_track_count() - 1); // Hack. if (h.type == Variant::FLOAT || h.type == Variant::VECTOR2 || h.type == Variant::RECT2 || @@ -4511,12 +4522,12 @@ void AnimationTrackEditor::_new_track_property_selected(String p_name) { } else { Vector<String> subindices; { - //hack + // Hack. NodePath np; animation->add_track(Animation::TYPE_VALUE); animation->track_set_path(animation->get_track_count() - 1, full_path); PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np); - animation->remove_track(animation->get_track_count() - 1); //hack + animation->remove_track(animation->get_track_count() - 1); // Hack. bool valid; subindices = _get_bezier_subindices_for_type(h.type, &valid); if (!valid) { @@ -4568,7 +4579,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { if (snap->is_pressed() && step->get_value() != 0) { p_ofs = snap_time(p_ofs); } - while (animation->track_find_key(p_track, p_ofs, true) != -1) { //make sure insertion point is valid + while (animation->track_find_key(p_track, p_ofs, true) != -1) { // Make sure insertion point is valid. p_ofs += 0.001; } @@ -4763,16 +4774,16 @@ struct _AnimMoveRestore { Variant key; float transition = 0; }; -//used for undo/redo +// Used for undo/redo. void AnimationTrackEditor::_clear_key_edit() { if (key_edit) { - //if key edit is the object being inspected, remove it first + // If key edit is the object being inspected, remove it first. if (EditorNode::get_singleton()->get_inspector()->get_edited_object() == key_edit) { EditorNode::get_singleton()->push_item(nullptr); } - //then actually delete it + // Then actually delete it. memdelete(key_edit); key_edit = nullptr; } @@ -4886,11 +4897,11 @@ void AnimationTrackEditor::_move_selection_commit() { List<_AnimMoveRestore> to_restore; float motion = moving_selection_offset; - // 1 - remove the keys + // 1 - remove the keys. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->key().track, E->key().key); } - // 2 - remove overlapped keys + // 2 - Remove overlapped keys. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float newtime = snap_time(E->get().pos + motion); int idx = animation->track_find_key(E->key().track, newtime, true); @@ -4901,7 +4912,7 @@ void AnimationTrackEditor::_move_selection_commit() { sk.key = idx; sk.track = E->key().track; if (selection.has(sk)) { - continue; //already in selection, don't save + continue; // Already in selection, don't save. } undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newtime); @@ -4915,24 +4926,24 @@ void AnimationTrackEditor::_move_selection_commit() { to_restore.push_back(amr); } - // 3 - move the keys (re insert them) + // 3 - Move the keys (Reinsert them). for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float newpos = snap_time(E->get().pos + motion); undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->key().track, newpos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); } - // 4 - (undo) remove inserted keys + // 4 - (Undo) Remove inserted keys. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float newpos = snap_time(E->get().pos + motion); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newpos); } - // 5 - (undo) reinsert keys + // 5 - (Undo) Reinsert keys. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->key().track, E->get().pos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); } - // 6 - (undo) reinsert overlapped keys + // 6 - (Undo) Reinsert overlapped keys. for (_AnimMoveRestore &amr : to_restore) { undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition); } @@ -4940,7 +4951,7 @@ void AnimationTrackEditor::_move_selection_commit() { undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); - // 7 - reselect + // 7 - Reselect. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float oldpos = E->get().pos; float newpos = snap_time(oldpos + motion); @@ -5010,18 +5021,18 @@ void AnimationTrackEditor::_scroll_input(const Ref<InputEvent> &p_event) { box_select_rect = Rect2(); } else if (box_selecting) { if (box_selection->is_visible_in_tree()) { - //only if moved + // Only if moved. for (int i = 0; i < track_edits.size(); i++) { Rect2 local_rect = box_select_rect; local_rect.position -= track_edits[i]->get_global_position(); track_edits[i]->append_to_selection(local_rect, mb->is_command_pressed()); } - if (_get_track_selected() == -1 && track_edits.size() > 0) { //minimal hack to make shortcuts work + if (_get_track_selected() == -1 && track_edits.size() > 0) { // Minimal hack to make shortcuts work. track_edits[track_edits.size() - 1]->grab_focus(); } } else { - _clear_selection(); //clear it + _clear_selection(); // Clear it. } box_selection->hide(); @@ -5037,7 +5048,7 @@ void AnimationTrackEditor::_scroll_input(const Ref<InputEvent> &p_event) { if (mm.is_valid() && box_selecting) { if (!(mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT)) { - //no longer + // No longer. box_selection->hide(); box_selecting = false; return; @@ -5077,16 +5088,16 @@ void AnimationTrackEditor::_cancel_bezier_edit() { } void AnimationTrackEditor::_bezier_edit(int p_for_track) { - _clear_selection(); //bezier probably wants to use a separate selection mode + _clear_selection(); // Bezier probably wants to use a separate selection mode. bezier_edit->set_root(root); bezier_edit->set_animation_and_track(animation, p_for_track); scroll->hide(); bezier_edit->show(); - //search everything within the track and curve- edit it + // Search everything within the track and curve - edit it. } void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) { - //duplicait! + // Duplicait! if (selection.size() && animation.is_valid() && (!transpose || (_get_track_selected() >= 0 && _get_track_selected() < animation->get_track_count()))) { int top_track = 0x7FFFFFFF; float top_time = 1e10; @@ -5144,7 +5155,7 @@ void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) { undo_redo->commit_action(); - //reselect duplicated + // Reselect duplicated. Map<SelectedKey, KeyInfo> new_selection; for (const Pair<int, float> &E : new_selection_values) { @@ -5173,7 +5184,7 @@ void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) { } void AnimationTrackEditor::_edit_menu_about_to_popup() { - AnimationPlayer *player = AnimationPlayerEditor::singleton->get_player(); + AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player(); edit->get_popup()->set_item_disabled(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), !player->can_apply_reset()); } @@ -5256,7 +5267,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { text += sn[j]; } - path = NodePath(node->get_path().get_names(), path.get_subnames(), true); //store full path instead for copying + path = NodePath(node->get_path().get_names(), path.get_subnames(), true); // Store full path instead for copying. } else { text = path; int sep = text.find(":"); @@ -5410,11 +5421,11 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { List<_AnimMoveRestore> to_restore; - // 1-remove the keys + // 1 - Remove the keys. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->key().track, E->key().key); } - // 2- remove overlapped keys + // 2 - Remove overlapped keys. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float newtime = (E->get().pos - from_t) * s + from_t; int idx = animation->track_find_key(E->key().track, newtime, true); @@ -5425,7 +5436,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { sk.key = idx; sk.track = E->key().track; if (selection.has(sk)) { - continue; //already in selection, don't save + continue; // Already in selection, don't save. } undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newtime); @@ -5440,24 +5451,24 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { } #define _NEW_POS(m_ofs) (((s > 0) ? m_ofs : from_t + (len - (m_ofs - from_t))) - pivot) * ABS(s) + from_t - // 3-move the keys (re insert them) + // 3 - Move the keys (re insert them). for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float newpos = _NEW_POS(E->get().pos); undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->key().track, newpos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); } - // 4-(undo) remove inserted keys + // 4 - (Undo) Remove inserted keys. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float newpos = _NEW_POS(E->get().pos); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newpos); } - // 5-(undo) reinsert keys + // 5 - (Undo) Reinsert keys. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->key().track, E->get().pos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); } - // 6-(undo) reinsert overlapped keys + // 6 - (Undo) Reinsert overlapped keys. for (_AnimMoveRestore &amr : to_restore) { undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition); } @@ -5465,7 +5476,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); - // 7-reselect + // 7-reselect. for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float oldpos = E->get().pos; float newpos = _NEW_POS(oldpos); @@ -5517,7 +5528,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { goto_prev_step(false); } break; case EDIT_APPLY_RESET: { - AnimationPlayerEditor::singleton->get_player()->apply_reset(true); + AnimationPlayerEditor::get_singleton()->get_player()->apply_reset(true); } break; case EDIT_OPTIMIZE_ANIMATION: { @@ -5537,9 +5548,9 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { case EDIT_CLEAN_UP_ANIMATION_CONFIRM: { if (cleanup_all->is_pressed()) { List<StringName> names; - AnimationPlayerEditor::singleton->get_player()->get_animation_list(&names); + AnimationPlayerEditor::get_singleton()->get_player()->get_animation_list(&names); for (const StringName &E : names) { - _cleanup_animation(AnimationPlayerEditor::singleton->get_player()->get_animation(E)); + _cleanup_animation(AnimationPlayerEditor::get_singleton()->get_player()->get_animation(E)); } } else { _cleanup_animation(animation); @@ -5614,7 +5625,7 @@ bool AnimationTrackEditor::is_grouping_tracks() { void AnimationTrackEditor::_selection_changed() { if (selected_filter->is_pressed()) { - _update_tracks(); //needs updatin + _update_tracks(); // Needs updatin. } else { for (int i = 0; i < track_edits.size(); i++) { track_edits[i]->update(); @@ -5683,7 +5694,6 @@ void AnimationTrackEditor::_bind_methods() { ClassDB::bind_method("_animation_update", &AnimationTrackEditor::_animation_update); ClassDB::bind_method("_track_grab_focus", &AnimationTrackEditor::_track_grab_focus); ClassDB::bind_method("_update_tracks", &AnimationTrackEditor::_update_tracks); - ClassDB::bind_method("_insert_delay", &AnimationTrackEditor::_insert_delay); ClassDB::bind_method("_clear_selection_for_anim", &AnimationTrackEditor::_clear_selection_for_anim); ClassDB::bind_method("_select_at_anim", &AnimationTrackEditor::_select_at_anim); @@ -5769,7 +5779,7 @@ AnimationTrackEditor::AnimationTrackEditor() { undo_redo = EditorNode::get_singleton()->get_undo_redo(); main_panel = memnew(PanelContainer); - main_panel->set_focus_mode(FOCUS_ALL); // allow panel to have focus so that shortcuts work as expected. + main_panel->set_focus_mode(FOCUS_ALL); // Allow panel to have focus so that shortcuts work as expected. add_child(main_panel); main_panel->set_v_size_flags(SIZE_EXPAND_FILL); HBoxContainer *timeline_scroll = memnew(HBoxContainer); @@ -5805,7 +5815,7 @@ AnimationTrackEditor::AnimationTrackEditor() { scroll->set_v_size_flags(SIZE_EXPAND_FILL); VScrollBar *sb = scroll->get_v_scrollbar(); scroll->remove_child(sb); - timeline_scroll->add_child(sb); //move here so timeline and tracks are always aligned + timeline_scroll->add_child(sb); // Move here so timeline and tracks are always aligned. scroll->connect("gui_input", callable_mp(this, &AnimationTrackEditor::_scroll_input)); bezier_edit = memnew(AnimationBezierTrackEdit); @@ -5846,7 +5856,7 @@ AnimationTrackEditor::AnimationTrackEditor() { selected_filter = memnew(Button); selected_filter->set_flat(true); - selected_filter->connect("pressed", callable_mp(this, &AnimationTrackEditor::_view_group_toggle)); //same function works the same + selected_filter->connect("pressed", callable_mp(this, &AnimationTrackEditor::_view_group_toggle)); // Same function works the same. selected_filter->set_toggle_mode(true); selected_filter->set_tooltip(TTR("Only show tracks from nodes selected in tree.")); @@ -5947,9 +5957,6 @@ AnimationTrackEditor::AnimationTrackEditor() { add_child(method_selector); method_selector->connect("selected", callable_mp(this, &AnimationTrackEditor::_add_method_key)); - inserting = false; - insert_query = false; - insert_frame = 0; insert_queue = false; insert_confirm = memnew(ConfirmationDialog); @@ -5982,13 +5989,13 @@ AnimationTrackEditor::AnimationTrackEditor() { box_selection->connect("draw", callable_mp(this, &AnimationTrackEditor::_box_selection_draw)); box_selecting = false; - //default plugins + // Default Plugins. Ref<AnimationTrackEditDefaultPlugin> def_plugin; def_plugin.instantiate(); add_track_edit_plugin(def_plugin); - //dialogs + // Dialogs. optimize_dialog = memnew(ConfirmationDialog); add_child(optimize_dialog); diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index 4da708dd1c..576edef0c8 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -352,10 +352,7 @@ class AnimationTrackEditor : public VBoxContainer { CheckBox *insert_confirm_reset; ConfirmationDialog *insert_confirm; bool insert_queue; - bool inserting; - bool insert_query; List<InsertData> insert_data; - uint64_t insert_frame; void _query_insert(const InsertData &p_id); Ref<Animation> _create_and_get_reset_animation(); @@ -370,7 +367,7 @@ class AnimationTrackEditor : public VBoxContainer { } }; TrackIndices _confirm_insert(InsertData p_id, TrackIndices p_next_tracks, bool p_create_reset, Ref<Animation> p_reset_anim, bool p_create_beziers); - void _insert_delay(bool p_create_reset, bool p_create_beziers); + void _insert_track(bool p_create_reset, bool p_create_beziers); void _root_removed(Node *p_root); @@ -531,6 +528,9 @@ public: void insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists = false); void insert_value_key(const String &p_property, const Variant &p_value, bool p_advance); void insert_transform_key(Node3D *p_node, const String &p_sub, const Transform3D &p_xform); + bool has_transform_track(Node3D *p_node, const String &p_sub); + void make_insert_queue(); + void commit_insert_queue(); void show_select_node_warning(bool p_show); diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 953989aadd..767856f939 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -1078,8 +1078,8 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const } for (int i = 0; i < p_dir->subdirs.size(); i++) { - if (updated_dir && !p_dir->subdirs[i]->verified) { - //this directory was removed, add action to remove it + if ((updated_dir && !p_dir->subdirs[i]->verified) || _should_skip_directory(p_dir->subdirs[i]->get_path())) { + //this directory was removed or ignored, add action to remove it ItemAction ia; ia.action = ItemAction::ACTION_DIR_REMOVE; ia.dir = p_dir->subdirs[i]; @@ -2163,7 +2163,7 @@ Error EditorFileSystem::_resource_import(const String &p_path) { } bool EditorFileSystem::_should_skip_directory(const String &p_path) { - if (p_path == ProjectSettings::get_singleton()->get_project_data_path()) { + if (p_path.begins_with(ProjectSettings::get_singleton()->get_project_data_path())) { return true; } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index a8fdca1b20..9b7dc966e6 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1776,19 +1776,25 @@ void EditorNode::restart_editor() { } void EditorNode::_save_all_scenes() { + bool all_saved = true; for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { Node *scene = editor_data.get_edited_scene_root(i); - if (scene && scene->get_scene_file_path() != "" && DirAccess::exists(scene->get_scene_file_path().get_base_dir())) { - if (i != editor_data.get_edited_scene()) { - _save_scene(scene->get_scene_file_path(), i); + if (scene) { + if (scene->get_scene_file_path() != "" && DirAccess::exists(scene->get_scene_file_path().get_base_dir())) { + if (i != editor_data.get_edited_scene()) { + _save_scene(scene->get_scene_file_path(), i); + } else { + _save_scene_with_preview(scene->get_scene_file_path()); + } } else { - _save_scene_with_preview(scene->get_scene_file_path()); + all_saved = false; } - } else { - show_warning(TTR("Could not save one or more scenes!"), TTR("Save All Scenes")); } } + if (!all_saved) { + show_warning(TTR("Could not save one or more scenes!"), TTR("Save All Scenes")); + } _save_default_environment(); } @@ -6928,7 +6934,7 @@ EditorNode::EditorNode() { add_child(editor_interface); //more visually meaningful to have this later - raise_bottom_panel_item(AnimationPlayerEditor::singleton); + raise_bottom_panel_item(AnimationPlayerEditor::get_singleton()); add_editor_plugin(VersionControlEditorPlugin::get_singleton()); add_editor_plugin(memnew(ShaderEditorPlugin(this))); @@ -7202,20 +7208,24 @@ bool EditorPluginList::forward_gui_input(const Ref<InputEvent> &p_event) { return discard; } -bool EditorPluginList::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled) { - bool discard = false; +EditorPlugin::AfterGUIInput EditorPluginList::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled) { + EditorPlugin::AfterGUIInput after = EditorPlugin::AFTER_GUI_INPUT_PASS; for (int i = 0; i < plugins_list.size(); i++) { if ((!serve_when_force_input_enabled) && plugins_list[i]->is_input_event_forwarding_always_enabled()) { continue; } - if (plugins_list[i]->forward_spatial_gui_input(p_camera, p_event)) { - discard = true; + EditorPlugin::AfterGUIInput current_after = plugins_list[i]->forward_spatial_gui_input(p_camera, p_event); + if (current_after == EditorPlugin::AFTER_GUI_INPUT_STOP) { + after = EditorPlugin::AFTER_GUI_INPUT_STOP; + } + if (after != EditorPlugin::AFTER_GUI_INPUT_STOP && current_after == EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + after = EditorPlugin::AFTER_GUI_INPUT_DESELECT; } } - return discard; + return after; } void EditorPluginList::forward_canvas_draw_over_viewport(Control *p_overlay) { diff --git a/editor/editor_node.h b/editor/editor_node.h index 07c834eeca..73feeecfee 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -942,7 +942,7 @@ public: bool forward_gui_input(const Ref<InputEvent> &p_event); void forward_canvas_draw_over_viewport(Control *p_overlay); void forward_canvas_force_draw_over_viewport(Control *p_overlay); - bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled); + EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled); void forward_spatial_draw_over_viewport(Control *p_overlay); void forward_spatial_force_draw_over_viewport(Control *p_overlay); void add_plugin(EditorPlugin *p_plugin); diff --git a/editor/editor_paths.cpp b/editor/editor_paths.cpp index 71f13c0c2f..5b48cc2638 100644 --- a/editor/editor_paths.cpp +++ b/editor/editor_paths.cpp @@ -200,6 +200,20 @@ EditorPaths::EditorPaths() { paths_valid = false; } } + + // Check that the project data directory '.gdignore' file exists + String project_data_gdignore_file_path = project_data_dir.plus_file(".gdignore"); + if (!FileAccess::exists(project_data_gdignore_file_path)) { + // Add an empty .gdignore file to avoid scan. + FileAccessRef f = FileAccess::open(project_data_gdignore_file_path, FileAccess::WRITE); + if (f) { + f->store_line(""); + f->close(); + } else { + ERR_PRINT("Failed to create file " + project_data_gdignore_file_path); + } + } + Engine::get_singleton()->set_shader_cache_path(project_data_dir); // Editor metadata dir. diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp index 7afc8d4c83..aee8322a97 100644 --- a/editor/editor_plugin.cpp +++ b/editor/editor_plugin.cpp @@ -593,14 +593,14 @@ int EditorPlugin::update_overlays() const { } } -bool EditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { - bool success; +EditorPlugin::AfterGUIInput EditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { + int success; if (GDVIRTUAL_CALL(_forward_3d_gui_input, p_camera, p_event, success)) { - return success; + return static_cast<EditorPlugin::AfterGUIInput>(success); } - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } void EditorPlugin::forward_spatial_draw_over_viewport(Control *p_overlay) { diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h index 169106d901..57830df327 100644 --- a/editor/editor_plugin.h +++ b/editor/editor_plugin.h @@ -151,7 +151,7 @@ protected: GDVIRTUAL1R(bool, _forward_canvas_gui_input, Ref<InputEvent>) GDVIRTUAL1(_forward_canvas_draw_over_viewport, Control *) GDVIRTUAL1(_forward_canvas_force_draw_over_viewport, Control *) - GDVIRTUAL2R(bool, _forward_3d_gui_input, Camera3D *, Ref<InputEvent>) + GDVIRTUAL2R(int, _forward_3d_gui_input, Camera3D *, Ref<InputEvent>) GDVIRTUAL1(_forward_3d_draw_over_viewport, Control *) GDVIRTUAL1(_forward_3d_force_draw_over_viewport, Control *) GDVIRTUAL0RC(String, _get_plugin_name) @@ -200,6 +200,12 @@ public: DOCK_SLOT_MAX }; + enum AfterGUIInput { + AFTER_GUI_INPUT_PASS, + AFTER_GUI_INPUT_STOP, + AFTER_GUI_INPUT_DESELECT + }; + //TODO: send a resource for editing to the editor node? void add_control_to_container(CustomControlContainer p_location, Control *p_control); @@ -228,7 +234,7 @@ public: virtual void forward_canvas_draw_over_viewport(Control *p_overlay); virtual void forward_canvas_force_draw_over_viewport(Control *p_overlay); - virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); + virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); virtual void forward_spatial_draw_over_viewport(Control *p_overlay); virtual void forward_spatial_force_draw_over_viewport(Control *p_overlay); diff --git a/editor/editor_resource_preview.cpp b/editor/editor_resource_preview.cpp index 8fc1345f3e..8783fe4fc0 100644 --- a/editor/editor_resource_preview.cpp +++ b/editor/editor_resource_preview.cpp @@ -439,7 +439,7 @@ void EditorResourcePreview::stop() { preview_sem.post(); while (!exited.is_set()) { OS::get_singleton()->delay_usec(10000); - RenderingServer::get_singleton()->sync(); //sync pending stuff, as thread may be blocked on visual server + RenderingServer::get_singleton()->sync(); //sync pending stuff, as thread may be blocked on rendering server } thread.wait_to_finish(); } diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 6efbcbc61e..2d4db48f2a 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -213,6 +213,7 @@ void editor_register_and_generate_icons(Ref<Theme> p_theme, bool p_dark_theme = exceptions.insert("EditorPivot"); exceptions.insert("EditorHandle"); exceptions.insert("Editor3DHandle"); + exceptions.insert("EditorBoneHandle"); exceptions.insert("Godot"); exceptions.insert("Sky"); exceptions.insert("EditorControlAnchor"); diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp index 283496c0f1..b61f6e12eb 100644 --- a/editor/find_in_files.cpp +++ b/editor/find_in_files.cpp @@ -235,7 +235,7 @@ void FindInFiles::_scan_dir(String path, PackedStringArray &out_folders) { // Ignore special dirs (such as .git and project data directory) String project_data_dir_name = ProjectSettings::get_singleton()->get_project_data_dir_name(); - if (file.begins_with(".") || file == project_data_dir_name) { + if (file.begins_with(".") || file.begins_with(project_data_dir_name)) { continue; } if (dir->current_is_hidden()) { diff --git a/editor/icons/EditorBoneHandle.svg b/editor/icons/EditorBoneHandle.svg new file mode 100644 index 0000000000..a6d7c3f878 --- /dev/null +++ b/editor/icons/EditorBoneHandle.svg @@ -0,0 +1 @@ +<svg height="8" viewBox="0 0 8 8" width="8" xmlns="http://www.w3.org/2000/svg"><circle cx="4" cy="4" fill="#fff" r="4"/><circle cx="4" cy="4" fill="#000" r="2.5"/></svg> diff --git a/editor/icons/ToolBoneSelect.svg b/editor/icons/ToolBoneSelect.svg new file mode 100644 index 0000000000..cc12b69a82 --- /dev/null +++ b/editor/icons/ToolBoneSelect.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" fill-opacity=".9961"><path d="m16 11.46-6.142-2.527-1.572-.647.647 1.572 2.527 6.142.913-2.72 1.815 1.817.91-.909-1.818-1.815z"/><path d="m7.784 11.008-.886-2.152c-.23-.56-.102-1.203.327-1.631.287-.287.67-.439 1.061-.439.192 0 .386.037.57.113l2.151.885.17-.17c.977.645 2.271.516 3.1-.311.964-.963.964-2.524 0-3.488-.377-.377-.867-.622-1.396-.697-.074-.529-.318-1.019-.695-1.397-.455-.453-1.067-.711-1.707-.721-.667-.01-1.309.25-1.782.72-.828.829-.96 2.126-.314 3.105l-3.558 3.561c-.978-.646-2.274-.515-3.103.312-.963.962-.963 2.524 0 3.487.378.377.868.621 1.396.695.075.529.319 1.02.696 1.396.963.964 2.525.964 3.488 0 .828-.828.96-2.125.314-3.104z"/></g></svg> diff --git a/editor/import/resource_importer_texture_atlas.cpp b/editor/import/resource_importer_texture_atlas.cpp index 36fd161c35..cf699599ae 100644 --- a/editor/import/resource_importer_texture_atlas.cpp +++ b/editor/import/resource_importer_texture_atlas.cpp @@ -74,6 +74,7 @@ String ResourceImporterTextureAtlas::get_preset_name(int p_idx) const { void ResourceImporterTextureAtlas::get_import_options(List<ImportOption> *r_options, int p_preset) const { r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "atlas_file", PROPERTY_HINT_SAVE_FILE, "*.png"), "")); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "import_mode", PROPERTY_HINT_ENUM, "Region,Mesh2D"), 0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "crop_to_region"), false)); } String ResourceImporterTextureAtlas::get_option_group_file() const { @@ -206,6 +207,7 @@ Error ResourceImporterTextureAtlas::import_group_file(const String &p_group_file ERR_CONTINUE(err != OK); pack_data.image = image; + pack_data.is_cropped = options["crop_to_region"]; int mode = options["import_mode"]; @@ -324,7 +326,10 @@ Error ResourceImporterTextureAtlas::import_group_file(const String &p_group_file atlas_texture.instantiate(); atlas_texture->set_atlas(cache); atlas_texture->set_region(Rect2(offset, pack_data.region.size)); - atlas_texture->set_margin(Rect2(pack_data.region.position, pack_data.image->get_size() - pack_data.region.size)); + + if (!pack_data.is_cropped) { + atlas_texture->set_margin(Rect2(pack_data.region.position, pack_data.image->get_size() - pack_data.region.size)); + } texture = atlas_texture; } else { diff --git a/editor/import/resource_importer_texture_atlas.h b/editor/import/resource_importer_texture_atlas.h index b675d12477..d518a120bf 100644 --- a/editor/import/resource_importer_texture_atlas.h +++ b/editor/import/resource_importer_texture_atlas.h @@ -38,6 +38,7 @@ class ResourceImporterTextureAtlas : public ResourceImporter { struct PackData { Rect2 region; + bool is_cropped = false; bool is_mesh = false; Vector<int> chart_pieces; //one for region, many for mesh Vector<Vector<Vector2>> chart_vertices; //for mesh diff --git a/editor/import_dock.cpp b/editor/import_dock.cpp index 5b52554335..a6e1e1d094 100644 --- a/editor/import_dock.cpp +++ b/editor/import_dock.cpp @@ -130,6 +130,7 @@ void ImportDock::set_edit_path(const String &p_path) { _add_keep_import_option(importer_name); import->set_disabled(false); + _set_dirty(false); import_as->set_disabled(false); preset->set_disabled(false); @@ -280,6 +281,7 @@ void ImportDock::set_edit_multiple_paths(const Vector<String> &p_paths) { params->paths = p_paths; import->set_disabled(false); + _set_dirty(false); import_as->set_disabled(false); preset->set_disabled(false); @@ -533,6 +535,8 @@ void ImportDock::_reimport() { EditorFileSystem::get_singleton()->reimport_files(params->paths); EditorFileSystem::get_singleton()->emit_signal(SNAME("filesystem_changed")); //it changed, so force emitting the signal + + _set_dirty(false); } void ImportDock::_notification(int p_what) { @@ -548,6 +552,24 @@ void ImportDock::_notification(int p_what) { } } +void ImportDock::_property_edited(const StringName &p_prop) { + _set_dirty(true); +} + +void ImportDock::_set_dirty(bool p_dirty) { + if (p_dirty) { + // Add a dirty marker to notify the user that they should reimport the selected resource to see changes. + import->set_text(TTR("Reimport") + " (*)"); + import->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), SNAME("Editor"))); + import->set_tooltip(TTR("You have pending changes that haven't been applied yet. Click Reimport to apply changes made to the import options.\nSelecting another resource in the FileSystem dock without clicking Reimport first will discard changes made in the Import dock.")); + } else { + // Remove the dirty marker on the Reimport button. + import->set_text(TTR("Reimport")); + import->remove_theme_color_override("font_color"); + import->set_tooltip(""); + } +} + void ImportDock::_property_toggled(const StringName &p_prop, bool p_checked) { if (p_checked) { params->checked.insert(p_prop); @@ -588,6 +610,7 @@ ImportDock::ImportDock() { import_opts = memnew(EditorInspector); add_child(import_opts); import_opts->set_v_size_flags(SIZE_EXPAND_FILL); + import_opts->connect("property_edited", callable_mp(this, &ImportDock::_property_edited)); import_opts->connect("property_toggled", callable_mp(this, &ImportDock::_property_toggled)); hb = memnew(HBoxContainer); diff --git a/editor/import_dock.h b/editor/import_dock.h index 3c28bbcd89..150c44576d 100644 --- a/editor/import_dock.h +++ b/editor/import_dock.h @@ -68,7 +68,9 @@ class ImportDock : public VBoxContainer { void _update_preset_menu(); void _add_keep_import_option(const String &p_importer_name); + void _property_edited(const StringName &p_prop); void _property_toggled(const StringName &p_prop, bool p_checked); + void _set_dirty(bool p_dirty); void _reimport_attempt(); void _reimport_and_restart(); void _reimport(); diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp index a559f05785..59d0b92ba0 100644 --- a/editor/inspector_dock.cpp +++ b/editor/inspector_dock.cpp @@ -383,7 +383,7 @@ void InspectorDock::_menu_expandall() { } void InspectorDock::_property_keyed(const String &p_keyed, const Variant &p_value, bool p_advance) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_value_key(p_keyed, p_value, p_advance); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_value_key(p_keyed, p_value, p_advance); } void InspectorDock::_transform_keyed(Object *sp, const String &p_sub, const Transform3D &p_key) { @@ -391,7 +391,7 @@ void InspectorDock::_transform_keyed(Object *sp, const String &p_sub, const Tran if (!s) { return; } - AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(s, p_sub, p_key); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_transform_key(s, p_sub, p_key); } void InspectorDock::_warning_pressed() { @@ -545,7 +545,7 @@ void InspectorDock::go_back() { void InspectorDock::update_keying() { bool valid = false; - if (AnimationPlayerEditor::singleton->get_track_editor()->has_keying()) { + if (AnimationPlayerEditor::get_singleton()->get_track_editor()->has_keying()) { EditorHistory *editor_history = EditorNode::get_singleton()->get_editor_history(); if (editor_history->get_path_size() >= 1) { Object *obj = ObjectDB::get_instance(editor_history->get_path_object(0)); diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 902b0aa9ec..ea025dad3e 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -298,7 +298,7 @@ void AnimationPlayerEditor::_animation_selected(int p_which) { autoplay->set_pressed(current == player->get_autoplay()); - AnimationPlayerEditor::singleton->get_track_editor()->update_keying(); + AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying(); EditorNode::get_singleton()->update_keying(); _animation_key_editor_seek(timeline_position, false); } @@ -826,7 +826,7 @@ void AnimationPlayerEditor::_update_player() { pin->set_disabled(player == nullptr); if (!player) { - AnimationPlayerEditor::singleton->get_track_editor()->update_keying(); + AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying(); EditorNode::get_singleton()->update_keying(); return; } diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index 0a514d3ff1..eb8db2eaba 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -128,6 +128,7 @@ class AnimationPlayerEditor : public VBoxContainer { bool updating_blends; AnimationTrackEditor *track_editor; + static AnimationPlayerEditor *singleton; // Onion skinning. struct { @@ -226,7 +227,8 @@ protected: public: AnimationPlayer *get_player() const; - static AnimationPlayerEditor *singleton; + + static AnimationPlayerEditor *get_singleton() { return singleton; } bool is_pinned() const { return pin->is_pressed(); } void unpin() { pin->set_pressed(false); } diff --git a/editor/plugins/animation_tree_editor_plugin.cpp b/editor/plugins/animation_tree_editor_plugin.cpp index cd84be0c25..6c5606fbfd 100644 --- a/editor/plugins/animation_tree_editor_plugin.cpp +++ b/editor/plugins/animation_tree_editor_plugin.cpp @@ -55,7 +55,7 @@ void AnimationTreeEditor::edit(AnimationTree *p_tree) { tree = p_tree; Vector<String> path; - if (tree->has_meta("_tree_edit_path")) { + if (tree && tree->has_meta("_tree_edit_path")) { path = tree->get_meta("_tree_edit_path"); edit_path(path); } else { diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 1d3986239c..8935f715e6 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -513,7 +513,7 @@ Object *CanvasItemEditor::_get_editor_data(Object *p_what) { } void CanvasItemEditor::_keying_changed() { - if (AnimationPlayerEditor::singleton->get_track_editor()->is_visible_in_tree()) { + if (AnimationPlayerEditor::get_singleton()->get_track_editor()->is_visible_in_tree()) { animation_hb->show(); } else { animation_hb->hide(); @@ -3828,7 +3828,7 @@ void CanvasItemEditor::_notification(int p_what) { select_sb->set_default_margin(Side(i), 4); } - AnimationPlayerEditor::singleton->get_track_editor()->connect("visibility_changed", callable_mp(this, &CanvasItemEditor::_keying_changed)); + AnimationPlayerEditor::get_singleton()->get_track_editor()->connect("visibility_changed", callable_mp(this, &CanvasItemEditor::_keying_changed)); _keying_changed(); } else if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { @@ -4285,13 +4285,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, Node2D *n2d = Object::cast_to<Node2D>(canvas_item); if (key_pos && p_location) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing); } if (key_rot && p_rotation) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "rotation", n2d->get_rotation(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(n2d, "rotation", n2d->get_rotation(), p_on_existing); } if (key_scale && p_scale) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing); } if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) { @@ -4317,13 +4317,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, if (has_chain && ik_chain.size()) { for (Node2D *&F : ik_chain) { if (key_pos) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F, "position", F->get_position(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(F, "position", F->get_position(), p_on_existing); } if (key_rot) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F, "rotation", F->get_rotation(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(F, "rotation", F->get_rotation(), p_on_existing); } if (key_scale) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing); } } } @@ -4333,13 +4333,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, Control *ctrl = Object::cast_to<Control>(canvas_item); if (key_pos) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), p_on_existing); } if (key_rot) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation(), p_on_existing); } if (key_scale) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), p_on_existing); } } } @@ -4779,7 +4779,7 @@ void CanvasItemEditor::_popup_callback(int p_op) { } /* if (key_scale) - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl,"rect/size",ctrl->get_size()); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl,"rect/size",ctrl->get_size()); */ } } diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.cpp b/editor/plugins/collision_polygon_3d_editor_plugin.cpp index 8b354c33a1..1ee834a974 100644 --- a/editor/plugins/collision_polygon_3d_editor_plugin.cpp +++ b/editor/plugins/collision_polygon_3d_editor_plugin.cpp @@ -103,9 +103,9 @@ void CollisionPolygon3DEditor::_wip_close() { undo_redo->commit_action(); } -bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { +EditorPlugin::AfterGUIInput CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { if (!node) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } Transform3D gt = node->get_global_transform(); @@ -124,7 +124,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con Vector3 spoint; if (!p.intersects_ray(ray_from, ray_dir, &spoint)) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } spoint = gi.xform(spoint); @@ -151,19 +151,19 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con snap_ignore = false; _polygon_draw(); edited_point = 1; - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else { if (wip.size() > 1 && p_camera->unproject_position(gt.xform(Vector3(wip[0].x, wip[0].y, depth))).distance_to(gpoint) < grab_threshold) { //wip closed _wip_close(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else { wip.push_back(cpoint); edited_point = wip.size(); snap_ignore = false; _polygon_draw(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed() && wip_active) { @@ -184,7 +184,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con undo_redo->add_do_method(this, "_polygon_draw"); undo_redo->add_undo_method(this, "_polygon_draw"); undo_redo->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } //search edges @@ -219,7 +219,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con _polygon_draw(); snap_ignore = true; - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } else { //look for points to move @@ -244,7 +244,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con edited_point_pos = poly[closest_idx]; _polygon_draw(); snap_ignore = false; - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } } else { @@ -253,7 +253,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con if (edited_point != -1) { //apply - ERR_FAIL_INDEX_V(edited_point, poly.size(), false); + ERR_FAIL_INDEX_V(edited_point, poly.size(), EditorPlugin::AFTER_GUI_INPUT_PASS); poly.write[edited_point] = edited_point_pos; undo_redo->create_action(TTR("Edit Poly")); undo_redo->add_do_method(node, "set_polygon", poly); @@ -263,7 +263,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con undo_redo->commit_action(); edited_point = -1; - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } } @@ -290,7 +290,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con undo_redo->add_do_method(this, "_polygon_draw"); undo_redo->add_undo_method(this, "_polygon_draw"); undo_redo->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } @@ -310,7 +310,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con Vector3 spoint; if (!p.intersects_ray(ray_from, ray_dir, &spoint)) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } spoint = gi.xform(spoint); @@ -332,7 +332,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con } } - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } float CollisionPolygon3DEditor::_get_depth() { diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.h b/editor/plugins/collision_polygon_3d_editor_plugin.h index 5db0f7308a..10b0adf76c 100644 --- a/editor/plugins/collision_polygon_3d_editor_plugin.h +++ b/editor/plugins/collision_polygon_3d_editor_plugin.h @@ -88,7 +88,7 @@ protected: static void _bind_methods(); public: - virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); + virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); void edit(Node *p_collision_polygon); CollisionPolygon3DEditor(EditorNode *p_editor); ~CollisionPolygon3DEditor(); @@ -101,7 +101,7 @@ class Polygon3DEditorPlugin : public EditorPlugin { EditorNode *editor; public: - virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return collision_polygon_editor->forward_spatial_gui_input(p_camera, p_event); } + virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return collision_polygon_editor->forward_spatial_gui_input(p_camera, p_event); } virtual String get_name() const override { return "Polygon3DEditor"; } bool has_main_screen() const override { return false; } diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index 7b0fc07fe7..4d2fc29fe0 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -832,7 +832,7 @@ void EditorNode3DGizmo::_bind_methods() { ClassDB::bind_method(D_METHOD("get_plugin"), &EditorNode3DGizmo::get_plugin); ClassDB::bind_method(D_METHOD("clear"), &EditorNode3DGizmo::clear); ClassDB::bind_method(D_METHOD("set_hidden", "hidden"), &EditorNode3DGizmo::set_hidden); - ClassDB::bind_method(D_METHOD("is_subgizmo_selected"), &EditorNode3DGizmo::is_subgizmo_selected); + ClassDB::bind_method(D_METHOD("is_subgizmo_selected", "id"), &EditorNode3DGizmo::is_subgizmo_selected); ClassDB::bind_method(D_METHOD("get_subgizmo_selection"), &EditorNode3DGizmo::get_subgizmo_selection); GDVIRTUAL_BIND(_redraw); @@ -2029,169 +2029,6 @@ void Position3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_collision_segments(cursor_points); } -///// - -Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() { - Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4)); - create_material("skeleton_material", gizmo_color); -} - -bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { - return Object::cast_to<Skeleton3D>(p_spatial) != nullptr; -} - -String Skeleton3DGizmoPlugin::get_gizmo_name() const { - return "Skeleton3D"; -} - -int Skeleton3DGizmoPlugin::get_priority() const { - return -1; -} - -void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Skeleton3D *skel = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); - - p_gizmo->clear(); - - Ref<Material> material = get_material("skeleton_material", p_gizmo); - - Ref<SurfaceTool> surface_tool(memnew(SurfaceTool)); - - surface_tool->begin(Mesh::PRIMITIVE_LINES); - surface_tool->set_material(material); - LocalVector<Transform3D> grests; - grests.resize(skel->get_bone_count()); - - LocalVector<int> bones; - LocalVector<float> weights; - bones.resize(4); - weights.resize(4); - - for (int i = 0; i < 4; i++) { - bones[i] = 0; - weights[i] = 0; - } - - weights[0] = 1; - - AABB aabb; - - Color bonecolor = Color(1.0, 0.4, 0.4, 0.3); - Color rootcolor = Color(0.4, 1.0, 0.4, 0.1); - - LocalVector<int> bones_to_process; - bones_to_process = skel->get_parentless_bones(); - - while (bones_to_process.size() > 0) { - int current_bone_idx = bones_to_process[0]; - bones_to_process.erase(current_bone_idx); - - LocalVector<int> child_bones_vector; - child_bones_vector = skel->get_bone_children(current_bone_idx); - int child_bones_size = child_bones_vector.size(); - - if (skel->get_bone_parent(current_bone_idx) < 0) { - grests[current_bone_idx] = skel->get_bone_rest(current_bone_idx); - } - - for (int i = 0; i < child_bones_size; i++) { - int child_bone_idx = child_bones_vector[i]; - - grests[child_bone_idx] = grests[current_bone_idx] * skel->get_bone_rest(child_bone_idx); - Vector3 v0 = grests[current_bone_idx].origin; - Vector3 v1 = grests[child_bone_idx].origin; - Vector3 d = (v1 - v0).normalized(); - real_t dist = v0.distance_to(v1); - - // Find closest axis. - int closest = -1; - real_t closest_d = 0.0; - for (int j = 0; j < 3; j++) { - real_t dp = Math::abs(grests[current_bone_idx].basis[j].normalized().dot(d)); - if (j == 0 || dp > closest_d) { - closest = j; - } - } - - // Find closest other. - Vector3 first; - Vector3 points[4]; - int point_idx = 0; - for (int j = 0; j < 3; j++) { - bones[0] = current_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(rootcolor); - surface_tool->add_vertex(v0 - grests[current_bone_idx].basis[j].normalized() * dist * 0.05); - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(rootcolor); - surface_tool->add_vertex(v0 + grests[current_bone_idx].basis[j].normalized() * dist * 0.05); - - if (j == closest) { - continue; - } - - Vector3 axis; - if (first == Vector3()) { - axis = d.cross(d.cross(grests[current_bone_idx].basis[j])).normalized(); - first = axis; - } else { - axis = d.cross(first).normalized(); - } - - for (int k = 0; k < 2; k++) { - if (k == 1) { - axis = -axis; - } - Vector3 point = v0 + d * dist * 0.2; - point += axis * dist * 0.1; - - bones[0] = current_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(v0); - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(point); - - bones[0] = current_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(point); - bones[0] = child_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(v1); - points[point_idx++] = point; - } - } - SWAP(points[1], points[2]); - for (int j = 0; j < 4; j++) { - bones[0] = current_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(points[j]); - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(points[(j + 1) % 4]); - } - - // Add the bone's children to the list of bones to be processed. - bones_to_process.push_back(child_bones_vector[i]); - } - } - - Ref<ArrayMesh> m = surface_tool->commit(); - p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skel->register_skin(Ref<Skin>())); -} - //// PhysicalBone3DGizmoPlugin::PhysicalBone3DGizmoPlugin() { diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h index 24b4a23d4b..d1aca4d92e 100644 --- a/editor/plugins/node_3d_editor_gizmos.h +++ b/editor/plugins/node_3d_editor_gizmos.h @@ -332,18 +332,6 @@ public: Position3DGizmoPlugin(); }; -class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin { - GDCLASS(Skeleton3DGizmoPlugin, EditorNode3DGizmoPlugin); - -public: - bool has_gizmo(Node3D *p_spatial) override; - String get_gizmo_name() const override; - int get_priority() const override; - void redraw(EditorNode3DGizmo *p_gizmo) override; - - Skeleton3DGizmoPlugin(); -}; - class PhysicalBone3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(PhysicalBone3DGizmoPlugin, EditorNode3DGizmoPlugin); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 3173d2c7f0..ea6ef8ab84 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -1291,24 +1291,31 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { return; //do NONE } + EditorPlugin::AfterGUIInput after = EditorPlugin::AFTER_GUI_INPUT_PASS; { EditorNode *en = editor; EditorPluginList *force_input_forwarding_list = en->get_editor_plugins_force_input_forwarding(); if (!force_input_forwarding_list->is_empty()) { - bool discard = force_input_forwarding_list->forward_spatial_gui_input(camera, p_event, true); - if (discard) { + EditorPlugin::AfterGUIInput discard = force_input_forwarding_list->forward_spatial_gui_input(camera, p_event, true); + if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) { return; } + if (discard == EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + after = EditorPlugin::AFTER_GUI_INPUT_DESELECT; + } } } { EditorNode *en = editor; EditorPluginList *over_plugin_list = en->get_editor_plugins_over(); if (!over_plugin_list->is_empty()) { - bool discard = over_plugin_list->forward_spatial_gui_input(camera, p_event, false); - if (discard) { + EditorPlugin::AfterGUIInput discard = over_plugin_list->forward_spatial_gui_input(camera, p_event, false); + if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) { return; } + if (discard == EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + after = EditorPlugin::AFTER_GUI_INPUT_DESELECT; + } } } @@ -1573,17 +1580,19 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { break; } - clicked = _select_ray(b->get_position()); + if (after != EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + clicked = _select_ray(b->get_position()); - //clicking is always deferred to either move or release + //clicking is always deferred to either move or release - clicked_wants_append = b->is_shift_pressed(); + clicked_wants_append = b->is_shift_pressed(); - if (clicked.is_null()) { - //default to regionselect - cursor.region_select = true; - cursor.region_begin = b->get_position(); - cursor.region_end = b->get_position(); + if (clicked.is_null()) { + //default to regionselect + cursor.region_select = true; + cursor.region_begin = b->get_position(); + cursor.region_end = b->get_position(); + } } surface->update(); @@ -1594,14 +1603,16 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { break; } - if (clicked.is_valid()) { - _select_clicked(false); - } + if (after != EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + if (clicked.is_valid()) { + _select_clicked(false); + } - if (cursor.region_select) { - _select_region(); - cursor.region_select = false; - surface->update(); + if (cursor.region_select) { + _select_region(); + cursor.region_select = false; + surface->update(); + } } if (_edit.mode != TRANSFORM_NONE) { @@ -2237,7 +2248,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { return; } - if (!AnimationPlayerEditor::singleton->get_track_editor()->has_keying()) { + if (!AnimationPlayerEditor::get_singleton()->get_track_editor()->has_keying()) { set_message(TTR("Keying is disabled (no key inserted).")); return; } @@ -6757,6 +6768,33 @@ void Node3DEditor::_request_gizmo(Object *p_obj) { } } +void Node3DEditor::_set_subgizmo_selection(Object *p_obj, Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform) { + if (p_id == -1) { + _clear_subgizmo_selection(p_obj); + return; + } + + Node3D *sp = nullptr; + if (p_obj) { + sp = Object::cast_to<Node3D>(p_obj); + } else { + sp = selected; + } + + if (!sp) { + return; + } + + Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); + if (se) { + se->subgizmos.clear(); + se->subgizmos.insert(p_id, p_transform); + se->gizmo = p_gizmo; + sp->update_gizmos(); + update_transform_gizmo(); + } +} + void Node3DEditor::_clear_subgizmo_selection(Object *p_obj) { Node3D *sp = nullptr; if (p_obj) { @@ -6882,7 +6920,6 @@ void Node3DEditor::_register_all_gizmos() { add_gizmo_plugin(Ref<OccluderInstance3DGizmoPlugin>(memnew(OccluderInstance3DGizmoPlugin))); add_gizmo_plugin(Ref<SoftDynamicBody3DGizmoPlugin>(memnew(SoftDynamicBody3DGizmoPlugin))); add_gizmo_plugin(Ref<Sprite3DGizmoPlugin>(memnew(Sprite3DGizmoPlugin))); - add_gizmo_plugin(Ref<Skeleton3DGizmoPlugin>(memnew(Skeleton3DGizmoPlugin))); add_gizmo_plugin(Ref<Position3DGizmoPlugin>(memnew(Position3DGizmoPlugin))); add_gizmo_plugin(Ref<RayCast3DGizmoPlugin>(memnew(RayCast3DGizmoPlugin))); add_gizmo_plugin(Ref<SpringArm3DGizmoPlugin>(memnew(SpringArm3DGizmoPlugin))); @@ -6907,6 +6944,7 @@ void Node3DEditor::_register_all_gizmos() { void Node3DEditor::_bind_methods() { ClassDB::bind_method("_get_editor_data", &Node3DEditor::_get_editor_data); ClassDB::bind_method("_request_gizmo", &Node3DEditor::_request_gizmo); + ClassDB::bind_method("_set_subgizmo_selection", &Node3DEditor::_set_subgizmo_selection); ClassDB::bind_method("_clear_subgizmo_selection", &Node3DEditor::_clear_subgizmo_selection); ClassDB::bind_method("_refresh_menu_icons", &Node3DEditor::_refresh_menu_icons); @@ -7733,6 +7771,13 @@ Vector3 Node3DEditor::snap_point(Vector3 p_target, Vector3 p_start) const { return p_target; } +bool Node3DEditor::is_gizmo_visible() const { + if (selected) { + return gizmo.visible && selected->is_transform_gizmo_visible(); + } + return gizmo.visible; +} + double Node3DEditor::get_translate_snap() const { double snap_value; if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index e8948071e6..2d5aeaa981 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -665,6 +665,7 @@ private: Node3D *selected; void _request_gizmo(Object *p_obj); + void _set_subgizmo_selection(Object *p_obj, Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform = Transform3D()); void _clear_subgizmo_selection(Object *p_obj = nullptr); static Node3DEditor *singleton; @@ -758,7 +759,7 @@ public: float get_fov() const { return settings_fov->get_value(); } Transform3D get_gizmo_transform() const { return gizmo.transform; } - bool is_gizmo_visible() const { return gizmo.visible; } + bool is_gizmo_visible() const; ToolMode get_tool_mode() const { return tool_mode; } bool are_local_coords_enabled() const { return tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->is_pressed(); } diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index bb0c270baa..0268b6e5ea 100644 --- a/editor/plugins/path_3d_editor_plugin.cpp +++ b/editor/plugins/path_3d_editor_plugin.cpp @@ -294,13 +294,13 @@ Path3DGizmo::Path3DGizmo(Path3D *p_path) { orig_out_length = 0; } -bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { +EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { if (!path) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } Ref<Curve3D> c = path->get_curve(); if (c.is_null()) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } Transform3D gt = path->get_global_transform(); Transform3D it = gt.affine_inverse(); @@ -329,14 +329,14 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref const Vector3 *r = v3a.ptr(); if (p_camera->unproject_position(gt.xform(c->get_point_position(0))).distance_to(mbpos) < click_dist) { - return false; //nope, existing + return EditorPlugin::AFTER_GUI_INPUT_PASS; //nope, existing } for (int i = 0; i < c->get_point_count() - 1; i++) { //find the offset and point index of the place to break up int j = idx; if (p_camera->unproject_position(gt.xform(c->get_point_position(i + 1))).distance_to(mbpos) < click_dist) { - return false; //nope, existing + return EditorPlugin::AFTER_GUI_INPUT_PASS; //nope, existing } while (j < rc && c->get_point_position(i + 1) != r[j]) { @@ -386,7 +386,7 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref ur->add_do_method(c.ptr(), "add_point", closest_seg_point, Vector3(), Vector3(), closest_seg + 1); ur->add_undo_method(c.ptr(), "remove_point", closest_seg + 1); ur->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else { Vector3 org; @@ -405,7 +405,7 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref ur->add_do_method(c.ptr(), "add_point", it.xform(inters), Vector3(), Vector3(), -1); ur->add_undo_method(c.ptr(), "remove_point", c->get_point_count()); ur->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } //add new at pos @@ -425,27 +425,27 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref ur->add_do_method(c.ptr(), "remove_point", i); ur->add_undo_method(c.ptr(), "add_point", c->get_point_position(i), c->get_point_in(i), c->get_point_out(i), i); ur->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else if (dist_to_p_out < click_dist) { UndoRedo *ur = editor->get_undo_redo(); ur->create_action(TTR("Remove Out-Control Point")); ur->add_do_method(c.ptr(), "set_point_out", i, Vector3()); ur->add_undo_method(c.ptr(), "set_point_out", i, c->get_point_out(i)); ur->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else if (dist_to_p_in < click_dist) { UndoRedo *ur = editor->get_undo_redo(); ur->create_action(TTR("Remove In-Control Point")); ur->add_do_method(c.ptr(), "set_point_in", i, Vector3()); ur->add_undo_method(c.ptr(), "set_point_in", i, c->get_point_in(i)); ur->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } } } - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } void Path3DEditorPlugin::edit(Object *p_object) { diff --git a/editor/plugins/path_3d_editor_plugin.h b/editor/plugins/path_3d_editor_plugin.h index b74d7cc59e..974234ba8f 100644 --- a/editor/plugins/path_3d_editor_plugin.h +++ b/editor/plugins/path_3d_editor_plugin.h @@ -100,7 +100,7 @@ public: Path3D *get_edited_path() { return path; } static Path3DEditorPlugin *singleton; - virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; + virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; virtual String get_name() const override { return "Path3D"; } bool has_main_screen() const override { return false; } diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index ccb63871b7..677a5f1f2c 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -214,6 +214,7 @@ Ref<EditorSyntaxHighlighter> EditorPlainTextSyntaxHighlighter::_create() const { void ScriptEditorBase::_bind_methods() { ClassDB::bind_method(D_METHOD("get_base_editor"), &ScriptEditorBase::get_base_editor); + ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &ScriptEditorBase::add_syntax_highlighter); ADD_SIGNAL(MethodInfo("name_changed")); ADD_SIGNAL(MethodInfo("edited_script_changed")); @@ -484,14 +485,32 @@ void ScriptEditor::_clear_execution(REF p_script) { void ScriptEditor::_set_breakpoint(REF p_script, int p_line, bool p_enabled) { Ref<Script> script = Object::cast_to<Script>(*p_script); if (script.is_valid() && (script->has_source_code() || script->get_path().is_resource_file())) { - if (edit(p_script, p_line, 0, false)) { - editor->push_item(p_script.ptr()); - - ScriptEditorBase *se = _get_current_editor(); - if (se) { + // Update if open. + for (int i = 0; i < tab_container->get_child_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + if (se && se->get_edited_resource()->get_path() == script->get_path()) { se->set_breakpoint(p_line, p_enabled); + return; + } + } + + // Handle closed. + Dictionary state = script_editor_cache->get_value(script->get_path(), "state"); + Array breakpoints; + if (state.has("breakpoints")) { + breakpoints = state["breakpoints"]; + } + + if (breakpoints.has(p_line)) { + if (!p_enabled) { + breakpoints.erase(p_line); } + } else if (p_enabled) { + breakpoints.push_back(p_line); } + state["breakpoints"] = breakpoints; + script_editor_cache->set_value(script->get_path(), "state", state); + EditorDebuggerNode::get_singleton()->set_breakpoint(script->get_path(), p_line + 1, false); } } @@ -502,6 +521,34 @@ void ScriptEditor::_clear_breakpoints() { se->clear_breakpoints(); } } + + // Clear from closed scripts. + List<String> cached_editors; + script_editor_cache->get_sections(&cached_editors); + for (const String &E : cached_editors) { + Array breakpoints = _get_cached_breakpoints_for_script(E); + for (int i = 0; i < breakpoints.size(); i++) { + EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoints[i] + 1, false); + } + + if (breakpoints.size() > 0) { + Dictionary state = script_editor_cache->get_value(E, "state"); + state["breakpoints"] = Array(); + script_editor_cache->set_value(E, "state", state); + } + } +} + +Array ScriptEditor::_get_cached_breakpoints_for_script(const String &p_path) const { + if (!ResourceLoader::exists(p_path, "Script") || p_path.begins_with("local://") || !script_editor_cache->has_section_key(p_path, "state")) { + return Array(); + } + + Dictionary state = script_editor_cache->get_value(p_path, "state"); + if (!state.has("breakpoints")) { + return Array(); + } + return state["breakpoints"]; } ScriptEditorBase *ScriptEditor::_get_current_editor() const { @@ -716,20 +763,24 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tselected); if (current) { - Ref<Script> script = current->get_edited_resource(); - if (p_save && script.is_valid()) { + RES file = current->get_edited_resource(); + if (p_save && file.is_valid()) { // Do not try to save internal scripts, but prompt to save in-memory // scripts which are not saved to disk yet (have empty path). - if (script->get_path().find("local://") == -1 && script->get_path().find("::") == -1) { + if (file->get_path().find("local://") == -1 && file->get_path().find("::") == -1) { save_current_script(); } } - if (script.is_valid()) { - if (!script->get_path().is_empty()) { + if (file.is_valid()) { + if (!file->get_path().is_empty()) { // Only saved scripts can be restored. - previous_scripts.push_back(script->get_path()); + previous_scripts.push_back(file->get_path()); + } + + Ref<Script> script = file; + if (script.is_valid()) { + notify_script_close(script); } - notify_script_close(script); } } @@ -756,6 +807,7 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { int idx = tab_container->get_current_tab(); if (current) { current->clear_edit_menu(); + _save_editor_state(current); } memdelete(tselected); if (idx >= tab_container->get_child_count()) { @@ -1032,7 +1084,6 @@ void ScriptEditor::_file_dialog_action(String p_file) { memdelete(file); if (EditorFileSystem::get_singleton()) { - const Vector<String> textfile_extensions = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false); if (textfile_extensions.has(p_file.get_extension())) { EditorFileSystem::get_singleton()->update_file(p_file); } @@ -1120,9 +1171,8 @@ void ScriptEditor::_menu_option(int p_option) { file_dialog_option = FILE_NEW_TEXTFILE; file_dialog->clear_filters(); - const Vector<String> textfile_ext = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false); - for (int i = 0; i < textfile_ext.size(); i++) { - file_dialog->add_filter("*." + textfile_ext[i] + " ; " + textfile_ext[i].to_upper()); + for (const String &E : textfile_extensions) { + file_dialog->add_filter("*." + E + " ; " + E.to_upper()); } file_dialog->popup_file_dialog(); file_dialog->set_title(TTR("New Text File...")); @@ -1140,9 +1190,8 @@ void ScriptEditor::_menu_option(int p_option) { file_dialog->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); } - const Vector<String> textfile_ext = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false); - for (int i = 0; i < textfile_ext.size(); i++) { - file_dialog->add_filter("*." + textfile_ext[i] + " ; " + textfile_ext[i].to_upper()); + for (const String &E : textfile_extensions) { + file_dialog->add_filter("*." + E + " ; " + E.to_upper()); } file_dialog->popup_file_dialog(); @@ -1485,6 +1534,7 @@ void ScriptEditor::_notification(int p_what) { editor->connect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop)); editor->connect("script_add_function_request", callable_mp(this, &ScriptEditor::_add_callback)); editor->connect("resource_saved", callable_mp(this, &ScriptEditor::_res_saved_callback)); + editor->get_filesystem_dock()->connect("files_moved", callable_mp(this, &ScriptEditor::_files_moved)); editor->get_filesystem_dock()->connect("file_removed", callable_mp(this, &ScriptEditor::_file_removed)); script_list->connect("item_selected", callable_mp(this, &ScriptEditor::_script_selected)); @@ -1494,6 +1544,7 @@ void ScriptEditor::_notification(int p_what) { EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ScriptEditor::_editor_settings_changed)); EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &ScriptEditor::_filesystem_changed)); + _editor_settings_changed(); [[fallthrough]]; } case NOTIFICATION_TRANSLATION_CHANGED: @@ -1597,6 +1648,7 @@ void ScriptEditor::notify_script_changed(const Ref<Script> &p_script) { } void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { + Set<String> loaded_scripts; for (int i = 0; i < tab_container->get_child_count(); i++) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); if (!se) { @@ -1609,6 +1661,7 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { } String base = script->get_path(); + loaded_scripts.insert(base); if (base.begins_with("local://") || base == "") { continue; } @@ -1618,6 +1671,20 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { p_breakpoints->push_back(base + ":" + itos((int)bpoints[j] + 1)); } } + + // Load breakpoints that are in closed scripts. + List<String> cached_editors; + script_editor_cache->get_sections(&cached_editors); + for (const String &E : cached_editors) { + if (loaded_scripts.has(E)) { + continue; + } + + Array breakpoints = _get_cached_breakpoints_for_script(E); + for (int i = 0; i < breakpoints.size(); i++) { + p_breakpoints->push_back(E + ":" + itos((int)breakpoints[i] + 1)); + } + } } void ScriptEditor::_members_overview_selected(int p_idx) { @@ -2053,7 +2120,7 @@ void ScriptEditor::_update_script_connections() { } } -Ref<TextFile> ScriptEditor::_load_text_file(const String &p_path, Error *r_error) { +Ref<TextFile> ScriptEditor::_load_text_file(const String &p_path, Error *r_error) const { if (r_error) { *r_error = ERR_FILE_CANT_OPEN; } @@ -2275,6 +2342,10 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra _add_recent_script(p_resource->get_path()); } + if (script_editor_cache->has_section(p_resource->get_path())) { + se->set_edit_state(script_editor_cache->get_value(p_resource->get_path(), "state")); + } + _sort_list_on_update = true; _update_script_names(); _save_layout(); @@ -2510,6 +2581,20 @@ void ScriptEditor::_add_callback(Object *p_obj, const String &p_function, const } } +void ScriptEditor::_save_editor_state(ScriptEditorBase *p_editor) { + if (restoring_layout) { + return; + } + + const String &path = p_editor->get_edited_resource()->get_path(); + if (!path.is_resource_file()) { + return; + } + + script_editor_cache->set_value(path, "state", p_editor->get_edit_state()); + // This is saved later when we save the editor layout. +} + void ScriptEditor::_save_layout() { if (restoring_layout) { return; @@ -2519,6 +2604,12 @@ void ScriptEditor::_save_layout() { } void ScriptEditor::_editor_settings_changed() { + textfile_extensions.clear(); + const Vector<String> textfile_ext = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false); + for (const String &E : textfile_ext) { + textfile_extensions.insert(E); + } + trim_trailing_whitespace_on_save = EditorSettings::get_singleton()->get("text_editor/behavior/files/trim_trailing_whitespace_on_save"); convert_indent_on_save = EditorSettings::get_singleton()->get("text_editor/behavior/files/convert_indent_on_save"); use_space_indentation = EditorSettings::get_singleton()->get("text_editor/behavior/indent/type"); @@ -2555,6 +2646,26 @@ void ScriptEditor::_filesystem_changed() { _update_script_names(); } +void ScriptEditor::_files_moved(const String &p_old_file, const String &p_new_file) { + if (!script_editor_cache->has_section(p_old_file)) { + return; + } + Variant state = script_editor_cache->get_value(p_old_file, "state"); + script_editor_cache->erase_section(p_old_file); + script_editor_cache->set_value(p_new_file, "state", state); + + // If Script, update breakpoints with debugger. + Array breakpoints = _get_cached_breakpoints_for_script(p_new_file); + for (int i = 0; i < breakpoints.size(); i++) { + int line = (int)breakpoints[i] + 1; + EditorDebuggerNode::get_singleton()->set_breakpoint(p_old_file, line, false); + if (!p_new_file.begins_with("local://") && ResourceLoader::exists(p_new_file, "Script")) { + EditorDebuggerNode::get_singleton()->set_breakpoint(p_new_file, line, true); + } + } + // This is saved later when we save the editor layout. +} + void ScriptEditor::_file_removed(const String &p_removed_file) { for (int i = 0; i < tab_container->get_child_count(); i++) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); @@ -2566,6 +2677,15 @@ void ScriptEditor::_file_removed(const String &p_removed_file) { _close_tab(i, false, false); } } + + // Check closed. + if (script_editor_cache->has_section(p_removed_file)) { + Array breakpoints = _get_cached_breakpoints_for_script(p_removed_file); + for (int i = 0; i < breakpoints.size(); i++) { + EditorDebuggerNode::get_singleton()->set_breakpoint(p_removed_file, (int)breakpoints[i] + 1, false); + } + script_editor_cache->erase_section(p_removed_file); + } } void ScriptEditor::_update_find_replace_bar() { @@ -2696,12 +2816,22 @@ bool ScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data if (file == "" || !FileAccess::exists(file)) { continue; } - Ref<Script> scr = ResourceLoader::load(file); - if (scr.is_valid()) { - return true; + if (ResourceLoader::exists(file, "Script")) { + Ref<Script> scr = ResourceLoader::load(file); + if (scr.is_valid()) { + return true; + } + } + + if (textfile_extensions.has(file.get_extension())) { + Error err; + Ref<TextFile> text_file = _load_text_file(file, &err); + if (text_file.is_valid() && err == OK) { + return true; + } } } - return true; + return false; } return false; @@ -2766,9 +2896,13 @@ void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Co if (file == "" || !FileAccess::exists(file)) { continue; } - Ref<Script> scr = ResourceLoader::load(file); - if (scr.is_valid()) { - edit(scr); + + if (!ResourceLoader::exists(file, "Script") && !textfile_extensions.has(file.get_extension())) { + continue; + } + + RES res = open_file(file); + if (res.is_valid()) { if (tab_container->get_child_count() > num_tabs_before) { tab_container->move_child(tab_container->get_child(tab_container->get_child_count() - 1), new_index); num_tabs_before = tab_container->get_child_count(); @@ -2916,6 +3050,7 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { restoring_layout = true; + Set<String> loaded_scripts; List<String> extensions; ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); @@ -2928,8 +3063,12 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { } if (!FileAccess::exists(path)) { + if (script_editor_cache->has_section(path)) { + script_editor_cache->erase_section(path); + } continue; } + loaded_scripts.insert(path); if (extensions.find(path.get_extension())) { Ref<Script> scr = ResourceLoader::load(path); @@ -2974,6 +3113,26 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { script_split->set_split_offset(p_layout->get_value("ScriptEditor", "split_offset")); } + // Remove any deleted editors that have been removed between launches. + // and if a Script, register breakpoints with the debugger. + List<String> cached_editors; + script_editor_cache->get_sections(&cached_editors); + for (const String &E : cached_editors) { + if (loaded_scripts.has(E)) { + continue; + } + + if (!FileAccess::exists(E)) { + script_editor_cache->erase_section(E); + continue; + } + + Array breakpoints = _get_cached_breakpoints_for_script(E); + for (int i = 0; i < breakpoints.size(); i++) { + EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoints[i] + 1, true); + } + } + restoring_layout = false; _update_script_names(); @@ -2991,11 +3150,8 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { continue; } - Dictionary script_info; - script_info["path"] = path; - script_info["state"] = se->get_edit_state(); - - scripts.push_back(script_info); + _save_editor_state(se); + scripts.push_back(path); } EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_child(i)); @@ -3008,6 +3164,9 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { p_layout->set_value("ScriptEditor", "open_scripts", scripts); p_layout->set_value("ScriptEditor", "open_help", helps); p_layout->set_value("ScriptEditor", "split_offset", script_split->get_split_offset()); + + // Save the cache. + script_editor_cache->save(EditorSettings::get_singleton()->get_project_settings_dir().plus_file("script_editor_cache.cfg")); } void ScriptEditor::_help_class_open(const String &p_class) { @@ -3374,6 +3533,9 @@ void ScriptEditor::_bind_methods() { ScriptEditor::ScriptEditor(EditorNode *p_editor) { current_theme = ""; + script_editor_cache.instantiate(); + script_editor_cache->load(EditorSettings::get_singleton()->get_project_settings_dir().plus_file("script_editor_cache.cfg")); + completion_cache = memnew(EditorScriptCodeCompletionCache); restoring_layout = false; waiting_update_names = false; diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index e0c7e668ce..8caebc1c8c 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -382,13 +382,17 @@ class ScriptEditor : public PanelContainer { void _script_created(Ref<Script> p_script); void _set_breakpoint(REF p_scrpt, int p_line, bool p_enabled); void _clear_breakpoints(); + Array _get_cached_breakpoints_for_script(const String &p_path) const; ScriptEditorBase *_get_current_editor() const; Array _get_open_script_editors() const; + Ref<ConfigFile> script_editor_cache; + void _save_editor_state(ScriptEditorBase *p_editor); void _save_layout(); void _editor_settings_changed(); void _filesystem_changed(); + void _files_moved(const String &p_old_file, const String &p_new_file); void _file_removed(const String &p_file); void _autosave_scripts(); void _update_autosave_timer(); @@ -449,7 +453,8 @@ class ScriptEditor : public PanelContainer { Ref<Script> _get_current_script(); Array _get_open_scripts() const; - Ref<TextFile> _load_text_file(const String &p_path, Error *r_error); + Set<String> textfile_extensions; + Ref<TextFile> _load_text_file(const String &p_path, Error *r_error) const; Error _save_text_file(Ref<TextFile> p_text_file, const String &p_path); void _on_find_in_files_requested(String text); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 701d75fb08..2c02389db2 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1341,8 +1341,6 @@ void ScriptTextEditor::_bind_methods() { ClassDB::bind_method("_get_drag_data_fw", &ScriptTextEditor::get_drag_data_fw); ClassDB::bind_method("_can_drop_data_fw", &ScriptTextEditor::can_drop_data_fw); ClassDB::bind_method("_drop_data_fw", &ScriptTextEditor::drop_data_fw); - - ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &ScriptTextEditor::add_syntax_highlighter); } Control *ScriptTextEditor::get_edit_menu() { @@ -1707,7 +1705,6 @@ void ScriptTextEditor::_enable_code_editor() { code_editor->connect("show_warnings_panel", callable_mp(this, &ScriptTextEditor::_show_warnings_panel)); code_editor->connect("validate_script", callable_mp(this, &ScriptTextEditor::_validate_script)); code_editor->connect("load_theme_settings", callable_mp(this, &ScriptTextEditor::_load_theme_settings)); - code_editor->get_text_editor()->connect("breakpoint_toggled", callable_mp(this, &ScriptTextEditor::_breakpoint_toggled)); code_editor->get_text_editor()->connect("symbol_lookup", callable_mp(this, &ScriptTextEditor::_lookup_symbol)); code_editor->get_text_editor()->connect("symbol_validate", callable_mp(this, &ScriptTextEditor::_validate_symbol)); code_editor->get_text_editor()->connect("gutter_added", callable_mp(this, &ScriptTextEditor::_update_gutter_indexes)); @@ -1847,6 +1844,7 @@ ScriptTextEditor::ScriptTextEditor() { code_editor->get_text_editor()->set_draw_breakpoints_gutter(true); code_editor->get_text_editor()->set_draw_executing_lines_gutter(true); + code_editor->get_text_editor()->connect("breakpoint_toggled", callable_mp(this, &ScriptTextEditor::_breakpoint_toggled)); connection_gutter = 1; code_editor->get_text_editor()->add_gutter(connection_gutter); diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 4e3ab5380b..531ffc6a73 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -42,6 +42,7 @@ #include "scene/3d/physics_body_3d.h" #include "scene/resources/capsule_shape_3d.h" #include "scene/resources/sphere_shape_3d.h" +#include "scene/resources/surface_tool.h" void BoneTransformEditor::create_editors() { const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor")); @@ -50,6 +51,11 @@ void BoneTransformEditor::create_editors() { section->setup("trf_properties", label, this, section_color, true); add_child(section); + 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); + key_button = memnew(Button); key_button->set_text(TTR("Key Transform")); key_button->set_visible(keyable); @@ -57,49 +63,40 @@ void BoneTransformEditor::create_editors() { 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); - - // Translation property + // Translation property. translation_property = memnew(EditorPropertyVector3()); translation_property->setup(-10000, 10000, 0.001f, true); translation_property->set_label("Translation"); translation_property->set_use_folding(true); - translation_property->set_read_only(false); translation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3)); section->get_vbox()->add_child(translation_property); - // Rotation property + // Rotation property. rotation_property = memnew(EditorPropertyVector3()); rotation_property->setup(-10000, 10000, 0.001f, true); rotation_property->set_label("Rotation Degrees"); rotation_property->set_use_folding(true); - rotation_property->set_read_only(false); rotation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3)); section->get_vbox()->add_child(rotation_property); - // Scale property + // Scale property. scale_property = memnew(EditorPropertyVector3()); scale_property->setup(-10000, 10000, 0.001f, true); scale_property->set_label("Scale"); scale_property->set_use_folding(true); - scale_property->set_read_only(false); scale_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3)); section->get_vbox()->add_child(scale_property); - // Transform/Matrix section + // Transform/Matrix section. transform_section = memnew(EditorInspectorSection); transform_section->setup("trf_properties_transform", "Matrix", this, section_color, true); section->get_vbox()->add_child(transform_section); - // Transform/Matrix property + // Transform/Matrix property. transform_property = memnew(EditorPropertyTransform3D()); transform_property->setup(-10000, 10000, 0.001f, true); transform_property->set_label("Transform"); transform_property->set_use_folding(true); - transform_property->set_read_only(false); transform_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_transform)); transform_section->get_vbox()->add_child(transform_property); } @@ -109,7 +106,7 @@ void BoneTransformEditor::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { create_editors(); key_button->connect("pressed", callable_mp(this, &BoneTransformEditor::_key_button_pressed)); - enabled_checkbox->connect("toggled", callable_mp(this, &BoneTransformEditor::_checkbox_toggled)); + enabled_checkbox->connect("pressed", callable_mp(this, &BoneTransformEditor::_checkbox_pressed)); [[fallthrough]]; } case NOTIFICATION_SORT_CHILDREN: { @@ -229,7 +226,7 @@ void BoneTransformEditor::_update_properties() { return; } - if (skeleton == nullptr) { + if (!skeleton) { return; } @@ -244,7 +241,7 @@ void BoneTransformEditor::_update_custom_pose_properties() { return; } - if (skeleton == nullptr) { + if (!skeleton) { return; } @@ -281,11 +278,32 @@ void BoneTransformEditor::set_target(const String &p_prop) { void BoneTransformEditor::set_keyable(const bool p_keyable) { keyable = p_keyable; +} + +void BoneTransformEditor::_update_key_button(const bool p_keyable) { + bool is_keyable = keyable && p_keyable; if (key_button) { - key_button->set_visible(p_keyable); + key_button->set_visible(is_keyable); } } +void BoneTransformEditor::set_properties_read_only(const bool p_readonly) { + enabled_checkbox->set_disabled(p_readonly); + enabled_checkbox->update(); +} + +void BoneTransformEditor::set_transform_read_only(const bool p_readonly) { + translation_property->set_read_only(p_readonly); + rotation_property->set_read_only(p_readonly); + scale_property->set_read_only(p_readonly); + transform_property->set_read_only(p_readonly); + translation_property->update(); + rotation_property->update(); + scale_property->update(); + transform_property->update(); + _update_key_button(!p_readonly); +} + void BoneTransformEditor::set_toggle_enabled(const bool p_enabled) { toggle_enabled = p_enabled; if (enabled_checkbox) { @@ -294,7 +312,7 @@ void BoneTransformEditor::set_toggle_enabled(const bool p_enabled) { } void BoneTransformEditor::_key_button_pressed() { - if (skeleton == nullptr) { + if (!skeleton) { return; } @@ -305,30 +323,152 @@ void BoneTransformEditor::_key_button_pressed() { return; } - // Need to normalize the basis before you key it Transform3D tform = compute_transform_from_vector3s(); - tform.orthonormalize(); - AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(skeleton, name, tform); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_transform_key(skeleton, name, tform); } -void BoneTransformEditor::_checkbox_toggled(const bool p_toggled) { +void BoneTransformEditor::_checkbox_pressed() { + if (!skeleton) { + return; + } + + const BoneId bone_id = property.get_slicec('/', 1).to_int(); if (enabled_checkbox) { - const String path = "bones/" + property.get_slicec('/', 1) + "/enabled"; - skeleton->set(path, p_toggled); + undo_redo->create_action(TTR("Set Pose Enabled")); + bool enabled = skeleton->is_bone_enabled(bone_id); + undo_redo->add_do_method(skeleton, "set_bone_enabled", bone_id, !enabled); + undo_redo->add_undo_method(skeleton, "set_bone_enabled", bone_id, enabled); + undo_redo->commit_action(); } } -void Skeleton3DEditor::_on_click_option(int p_option) { +Skeleton3DEditor *Skeleton3DEditor::singleton = nullptr; + +void Skeleton3DEditor::set_keyable(const bool p_keyable) { + keyable = p_keyable; + skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_INSERT_KEYS, !p_keyable); + skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_INSERT_KEYS_EXISTED, !p_keyable); +}; + +void Skeleton3DEditor::set_rest_options_enabled(const bool p_rest_options_enabled) { + rest_options->get_popup()->set_item_disabled(REST_OPTION_POSE_TO_REST, !p_rest_options_enabled); +}; + +void Skeleton3DEditor::_update_show_rest_only() { + _update_pose_enabled(-1); +} + +void Skeleton3DEditor::_update_pose_enabled(int p_bone) { if (!skeleton) { return; } + if (pose_editor) { + pose_editor->set_properties_read_only(skeleton->is_show_rest_only()); - switch (p_option) { - case MENU_OPTION_CREATE_PHYSICAL_SKELETON: { + if (selected_bone > 0) { + pose_editor->set_transform_read_only(skeleton->is_show_rest_only() || !(skeleton->is_bone_enabled(selected_bone))); + } + } + _update_gizmo_visible(); +} + +void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) { + if (!skeleton) { + return; + } + + switch (p_skeleton_option) { + case SKELETON_OPTION_CREATE_PHYSICAL_SKELETON: { create_physical_skeleton(); break; } + case SKELETON_OPTION_INIT_POSE: { + init_pose(); + break; + } + case SKELETON_OPTION_INSERT_KEYS: { + insert_keys(true); + break; + } + case SKELETON_OPTION_INSERT_KEYS_EXISTED: { + insert_keys(false); + break; + } + } +} + +void Skeleton3DEditor::_on_click_rest_option(int p_rest_option) { + if (!skeleton) { + return; + } + + switch (p_rest_option) { + case REST_OPTION_POSE_TO_REST: { + pose_to_rest(); + break; + } + } +} + +void Skeleton3DEditor::init_pose() { + const int bone_len = skeleton->get_bone_count(); + if (!bone_len) { + return; + } + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); + for (int i = 0; i < bone_len; i++) { + ur->add_do_method(skeleton, "set_bone_pose", i, Transform3D()); + ur->add_undo_method(skeleton, "set_bone_pose", i, skeleton->get_bone_pose(i)); + } + ur->commit_action(); +} + +void Skeleton3DEditor::insert_keys(bool p_all_bones) { + if (!skeleton) { + return; + } + + int bone_len = skeleton->get_bone_count(); + Node *root = EditorNode::get_singleton()->get_tree()->get_root(); + String path = root->get_path_to(skeleton); + + AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); + te->make_insert_queue(); + for (int i = 0; i < bone_len; i++) { + const String name = skeleton->get_bone_name(i); + + if (name.is_empty()) { + continue; + } + + if (!p_all_bones && !te->has_transform_track(skeleton, name)) { + continue; + } + + Transform3D tform = skeleton->get_bone_pose(i); + te->insert_transform_key(skeleton, name, tform); + } + te->commit_insert_queue(); +} + +void Skeleton3DEditor::pose_to_rest() { + if (!skeleton) { + return; } + + // Todo: Do method with multiple bone selection. + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); + + ur->add_do_method(skeleton, "set_bone_pose", selected_bone, Transform3D()); + ur->add_undo_method(skeleton, "set_bone_pose", selected_bone, skeleton->get_bone_pose(selected_bone)); + ur->add_do_method(skeleton, "set_bone_custom_pose", selected_bone, Transform3D()); + ur->add_undo_method(skeleton, "set_bone_custom_pose", selected_bone, skeleton->get_bone_custom_pose(selected_bone)); + ur->add_do_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_rest(selected_bone) * skeleton->get_bone_custom_pose(selected_bone) * skeleton->get_bone_pose(selected_bone)); + ur->add_undo_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_rest(selected_bone)); + + ur->commit_action(); } void Skeleton3DEditor::create_physical_skeleton() { @@ -356,7 +496,7 @@ void Skeleton3DEditor::create_physical_skeleton() { bones_infos.write[bone_id].relative_rest = bones_infos[parent].relative_rest * skeleton->get_bone_rest(bone_id); - /// create physical bone on parent + // Create physical bone on parent. if (!bones_infos[parent].physical_bone) { bones_infos.write[parent].physical_bone = create_physical_bone(parent, bone_id, bones_infos); @@ -370,7 +510,7 @@ void Skeleton3DEditor::create_physical_skeleton() { bones_infos[parent].physical_bone->set_owner(owner); bones_infos[parent].physical_bone->get_child(0)->set_owner(owner); // set shape owner - /// Create joint between parent of parent + // Create joint between parent of parent. if (-1 != parent_parent) { bones_infos[parent].physical_bone->set_joint_type(PhysicalBone3D::JOINT_TYPE_PIN); } @@ -483,7 +623,7 @@ void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_se 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 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(); @@ -505,24 +645,30 @@ void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_se void Skeleton3DEditor::_joint_tree_selection_changed() { TreeItem *selected = joint_tree->get_selected(); - const String path = selected->get_metadata(0); + if (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) + "/"; + 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_target(bone_path + "pose"); + rest_editor->set_target(bone_path + "rest"); + custom_pose_editor->set_target(bone_path + "custom_pose"); - _update_properties(); + pose_editor->set_visible(true); + rest_editor->set_visible(true); + custom_pose_editor->set_visible(true); - pose_editor->set_visible(true); - rest_editor->set_visible(true); - custom_pose_editor->set_visible(true); + selected_bone = b_idx; + } } + set_rest_options_enabled(selected); + _update_properties(); + _update_pose_enabled(); } +// May be not used with single select mode. void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos) { } @@ -536,12 +682,13 @@ void Skeleton3DEditor::_update_properties() { if (custom_pose_editor) { custom_pose_editor->_update_custom_pose_properties(); } + _update_gizmo_transform(); } void Skeleton3DEditor::update_joint_tree() { joint_tree->clear(); - if (skeleton == nullptr) { + if (!skeleton) { return; } @@ -569,7 +716,7 @@ void Skeleton3DEditor::update_joint_tree() { joint_item->set_selectable(0, true); joint_item->set_metadata(0, "bones/" + itos(current_bone_idx)); - // Add the bone's children to the list of bones to be processed + // Add the bone's children to the list of bones to be processed. Vector<int> current_bone_child_bones = skeleton->get_bone_children(current_bone_idx); int child_bone_size = current_bone_child_bones.size(); for (int i = 0; i < child_bone_size; i++) { @@ -587,16 +734,56 @@ void Skeleton3DEditor::create_editors() { set_focus_mode(FOCUS_ALL); - // Create Top Menu Bar - options = memnew(MenuButton); - Node3DEditor::get_singleton()->add_control_to_menu_panel(options); + Node3DEditor *ne = Node3DEditor::get_singleton(); + AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); - options->set_text(TTR("Skeleton3D")); - options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton3D"), SNAME("EditorIcons"))); + // Create Top Menu Bar. + separator = memnew(VSeparator); + ne->add_control_to_menu_panel(separator); - options->get_popup()->add_item(TTR("Create physical skeleton"), MENU_OPTION_CREATE_PHYSICAL_SKELETON); + // Create Skeleton Option in Top Menu Bar. + skeleton_options = memnew(MenuButton); + ne->add_control_to_menu_panel(skeleton_options); - options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_option)); + skeleton_options->set_text(TTR("Skeleton3D")); + skeleton_options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton3D"), SNAME("EditorIcons"))); + + skeleton_options->get_popup()->add_item(TTR("Init pose"), SKELETON_OPTION_INIT_POSE); + skeleton_options->get_popup()->add_item(TTR("Insert key of all bone poses"), SKELETON_OPTION_INSERT_KEYS); + skeleton_options->get_popup()->add_item(TTR("Insert key of bone poses already exist track"), SKELETON_OPTION_INSERT_KEYS_EXISTED); + skeleton_options->get_popup()->add_item(TTR("Create physical skeleton"), SKELETON_OPTION_CREATE_PHYSICAL_SKELETON); + + skeleton_options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_skeleton_option)); + + // Create Rest Option in Top Menu Bar. + rest_options = memnew(MenuButton); + ne->add_control_to_menu_panel(rest_options); + + rest_options->set_text(TTR("Edit Rest")); + rest_options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("BoneAttachment3D"), SNAME("EditorIcons"))); + + rest_options->get_popup()->add_item(TTR("Apply current pose to rest"), REST_OPTION_POSE_TO_REST); + rest_options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_rest_option)); + set_rest_options_enabled(false); + + Vector<Variant> button_binds; + button_binds.resize(1); + + edit_mode_button = memnew(Button); + ne->add_control_to_menu_panel(edit_mode_button); + edit_mode_button->set_tooltip(TTR("Edit Mode\nShow buttons on joints.")); + edit_mode_button->set_toggle_mode(true); + edit_mode_button->set_flat(true); + edit_mode_button->connect("toggled", callable_mp(this, &Skeleton3DEditor::edit_mode_toggled)); + + edit_mode = false; + + set_keyable(te->has_keying()); + + if (skeleton) { + skeleton->add_child(handles_mesh_instance); + handles_mesh_instance->set_skeleton_path(NodePath("")); + } const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor")); @@ -612,7 +799,7 @@ void Skeleton3DEditor::create_editors() { joint_tree = memnew(Tree); joint_tree->set_columns(1); - joint_tree->set_focus_mode(Control::FocusMode::FOCUS_NONE); + joint_tree->set_focus_mode(Control::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); @@ -623,8 +810,8 @@ void Skeleton3DEditor::create_editors() { 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_keyable(te->has_keying()); pose_editor->set_visible(false); add_child(pose_editor); @@ -632,27 +819,34 @@ void Skeleton3DEditor::create_editors() { rest_editor->set_label(TTR("Bone Rest")); rest_editor->set_visible(false); add_child(rest_editor); + rest_editor->set_transform_read_only(true); 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); + custom_pose_editor->set_transform_read_only(true); } void Skeleton3DEditor::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_READY: { + edit_mode_button->set_icon(get_theme_icon("ToolBoneSelect", "EditorIcons")); + get_tree()->connect("node_removed", callable_mp(this, &Skeleton3DEditor::_node_removed), Vector<Variant>(), Object::CONNECT_ONESHOT); + break; + } case NOTIFICATION_ENTER_TREE: { create_editors(); update_joint_tree(); update_editors(); - - get_tree()->connect("node_removed", callable_mp(this, &Skeleton3DEditor::_node_removed), Vector<Variant>(), Object::CONNECT_ONESHOT); joint_tree->connect("item_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_selection_changed)); joint_tree->connect("item_rmb_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_rmb_select)); #ifdef TOOLS_ENABLED + skeleton->connect("pose_updated", callable_mp(this, &Skeleton3DEditor::_draw_gizmo)); skeleton->connect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties)); -#endif // TOOLS_ENABLED - + skeleton->connect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_update_pose_enabled)); + skeleton->connect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_show_rest_only)); +#endif break; } } @@ -661,7 +855,8 @@ void Skeleton3DEditor::_notification(int p_what) { void Skeleton3DEditor::_node_removed(Node *p_node) { if (skeleton && p_node == skeleton) { skeleton = nullptr; - options->hide(); + skeleton_options->hide(); + rest_options->hide(); } _update_properties(); @@ -671,24 +866,237 @@ 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_show_rest_only"), &Skeleton3DEditor::_update_show_rest_only); + ClassDB::bind_method(D_METHOD("_update_pose_enabled"), &Skeleton3DEditor::_update_pose_enabled); 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("_on_click_skeleton_option"), &Skeleton3DEditor::_on_click_skeleton_option); + ClassDB::bind_method(D_METHOD("_on_click_rest_option"), &Skeleton3DEditor::_on_click_rest_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("_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); + + ClassDB::bind_method(D_METHOD("_draw_gizmo"), &Skeleton3DEditor::_draw_gizmo); +} + +void Skeleton3DEditor::edit_mode_toggled(const bool pressed) { + edit_mode = pressed; + _update_gizmo_visible(); } Skeleton3DEditor::Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton3D *p_skeleton) : editor(p_editor), editor_plugin(e_plugin), skeleton(p_skeleton) { + singleton = this; + + // Handle. + handle_material = Ref<ShaderMaterial>(memnew(ShaderMaterial)); + handle_shader = Ref<Shader>(memnew(Shader)); + handle_shader->set_code(R"( +// Skeleton 3D gizmo handle shader. + +shader_type spatial; +render_mode unshaded, shadows_disabled, depth_draw_always; +uniform sampler2D texture_albedo : hint_albedo; +uniform float point_size : hint_range(0,128) = 32; +void vertex() { + if (!OUTPUT_IS_SRGB) { + COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); + } + VERTEX = VERTEX; + POSITION=PROJECTION_MATRIX*INV_CAMERA_MATRIX*WORLD_MATRIX*vec4(VERTEX.xyz,1.0); + POSITION.z = mix(POSITION.z, 0, 0.999); + POINT_SIZE = point_size; +} +void fragment() { + vec4 albedo_tex = texture(texture_albedo,POINT_COORD); + vec3 col = albedo_tex.rgb + COLOR.rgb; + col = vec3(min(col.r,1.0),min(col.g,1.0),min(col.b,1.0)); + ALBEDO = col; + if (albedo_tex.a < 0.5) { discard; } + ALPHA = albedo_tex.a; +} +)"); + handle_material->set_shader(handle_shader); + Ref<Texture2D> handle = editor->get_gui_base()->get_theme_icon("EditorBoneHandle", "EditorIcons"); + handle_material->set_shader_param("point_size", handle->get_width()); + handle_material->set_shader_param("texture_albedo", handle); + + handles_mesh_instance = memnew(MeshInstance3D); + handles_mesh_instance->set_cast_shadows_setting(GeometryInstance3D::SHADOW_CASTING_SETTING_OFF); + handles_mesh.instantiate(); + handles_mesh_instance->set_mesh(handles_mesh); +} + +void Skeleton3DEditor::update_bone_original() { + if (!skeleton) { + return; + } + if (skeleton->get_bone_count() == 0 || selected_bone == -1) { + return; + } + bone_original = skeleton->get_bone_pose(selected_bone); +} + +void Skeleton3DEditor::_hide_handles() { + handles_mesh_instance->hide(); +} + +void Skeleton3DEditor::_draw_gizmo() { + if (!skeleton) { + return; + } + + // If you call get_bone_global_pose() while drawing the surface, such as toggle rest mode, + // the skeleton update will be done first and + // the drawing surface will be interrupted once and an error will occur. + skeleton->force_update_all_dirty_bones(); + + // Handles. + if (edit_mode) { + _draw_handles(); + } else { + _hide_handles(); + } +} + +void Skeleton3DEditor::_draw_handles() { + handles_mesh_instance->show(); + + const int bone_len = skeleton->get_bone_count(); + handles_mesh->clear_surfaces(); + handles_mesh->surface_begin(Mesh::PRIMITIVE_POINTS); + + for (int i = 0; i < bone_len; i++) { + Color c; + if (i == selected_bone) { + c = Color(1, 1, 0); + } else { + c = Color(0.1, 0.25, 0.8); + } + Vector3 point = skeleton->get_bone_global_pose(i).origin; + handles_mesh->surface_set_color(c); + handles_mesh->surface_add_vertex(point); + } + handles_mesh->surface_end(); + handles_mesh->surface_set_material(0, handle_material); +} + +TreeItem *Skeleton3DEditor::_find(TreeItem *p_node, const NodePath &p_path) { + if (!p_node) { + return nullptr; + } + + NodePath np = p_node->get_metadata(0); + if (np == p_path) { + return p_node; + } + + TreeItem *children = p_node->get_first_child(); + while (children) { + TreeItem *n = _find(children, p_path); + if (n) { + return n; + } + children = children->get_next(); + } + + return nullptr; +} + +void Skeleton3DEditor::_subgizmo_selection_change() { + if (!skeleton) { + return; + } + + // Once validated by subgizmos_intersect_ray, but required if through inspector's bones tree. + if (!edit_mode) { + skeleton->clear_subgizmo_selection(); + return; + } + + int selected = -1; + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + if (se) { + selected = se->get_selected_bone(); + } + + if (selected >= 0) { + Vector<Ref<Node3DGizmo>> gizmos = skeleton->get_gizmos(); + for (int i = 0; i < gizmos.size(); i++) { + Ref<EditorNode3DGizmo> gizmo = gizmos[i]; + if (!gizmo.is_valid()) { + continue; + } + Ref<Skeleton3DGizmoPlugin> plugin = gizmo->get_plugin(); + if (!plugin.is_valid()) { + continue; + } + skeleton->set_subgizmo_selection(gizmo, selected, skeleton->get_bone_global_pose(selected)); + break; + } + } else { + skeleton->clear_subgizmo_selection(); + } +} + +void Skeleton3DEditor::select_bone(int p_idx) { + if (p_idx >= 0) { + TreeItem *ti = _find(joint_tree->get_root(), "bones/" + itos(p_idx)); + if (ti) { + // Make visible when it's collapsed. + TreeItem *node = ti->get_parent(); + while (node && node != joint_tree->get_root()) { + node->set_collapsed(false); + node = node->get_parent(); + } + ti->select(0); + joint_tree->scroll_to_item(ti); + } + } else { + selected_bone = -1; + joint_tree->deselect_all(); + _joint_tree_selection_changed(); + } } Skeleton3DEditor::~Skeleton3DEditor() { - if (options) { - Node3DEditor::get_singleton()->remove_control_from_menu_panel(options); + if (skeleton) { +#ifdef TOOLS_ENABLED + skeleton->disconnect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_show_rest_only)); + skeleton->disconnect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_update_pose_enabled)); + skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_draw_gizmo)); + skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties)); + skeleton->set_transform_gizmo_visible(true); +#endif + handles_mesh_instance->get_parent()->remove_child(handles_mesh_instance); + } + + handles_mesh_instance->queue_delete(); + + Node3DEditor *ne = Node3DEditor::get_singleton(); + + if (separator) { + ne->remove_control_from_menu_panel(separator); + memdelete(separator); + } + + if (skeleton_options) { + ne->remove_control_from_menu_panel(skeleton_options); + memdelete(skeleton_options); + } + + if (rest_options) { + ne->remove_control_from_menu_panel(rest_options); + memdelete(rest_options); + } + + if (edit_mode_button) { + ne->remove_control_from_menu_panel(edit_mode_button); + memdelete(edit_mode_button); } } @@ -700,16 +1108,413 @@ 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)); + skel_editor = memnew(Skeleton3DEditor(this, editor, skeleton)); add_custom_control(skel_editor); } Skeleton3DEditorPlugin::Skeleton3DEditorPlugin(EditorNode *p_node) { editor = p_node; - Ref<EditorInspectorPluginSkeleton> skeleton_plugin; - skeleton_plugin.instantiate(); + skeleton_plugin = memnew(EditorInspectorPluginSkeleton); skeleton_plugin->editor = editor; EditorInspector::add_inspector_plugin(skeleton_plugin); + + Ref<Skeleton3DGizmoPlugin> gizmo_plugin = Ref<Skeleton3DGizmoPlugin>(memnew(Skeleton3DGizmoPlugin)); + Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin); +} + +EditorPlugin::AfterGUIInput Skeleton3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + Node3DEditor *ne = Node3DEditor::get_singleton(); + if (se->is_edit_mode()) { + const Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (ne->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) { + if (!ne->is_gizmo_visible()) { + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } + } + if (mb->is_pressed()) { + se->update_bone_original(); + } + } + return EditorPlugin::AFTER_GUI_INPUT_DESELECT; + } + return EditorPlugin::AFTER_GUI_INPUT_PASS; +} + +bool Skeleton3DEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("Skeleton3D"); +} + +void Skeleton3DEditor::_update_gizmo_transform() { + Node3DEditor::get_singleton()->update_transform_gizmo(); +}; + +void Skeleton3DEditor::_update_gizmo_visible() { + _subgizmo_selection_change(); + if (edit_mode) { + if (selected_bone == -1) { +#ifdef TOOLS_ENABLED + skeleton->set_transform_gizmo_visible(false); +#endif + } else { +#ifdef TOOLS_ENABLED + if (skeleton->is_bone_enabled(selected_bone) && !skeleton->is_show_rest_only()) { + skeleton->set_transform_gizmo_visible(true); + } else { + skeleton->set_transform_gizmo_visible(false); + } +#endif + } + } else { +#ifdef TOOLS_ENABLED + skeleton->set_transform_gizmo_visible(true); +#endif + } + _draw_gizmo(); +} + +int Skeleton3DEditor::get_selected_bone() const { + return selected_bone; +} + +Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() { + unselected_mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + + selected_mat = Ref<ShaderMaterial>(memnew(ShaderMaterial)); + selected_sh = Ref<Shader>(memnew(Shader)); + selected_sh->set_code(R"( +// Skeleton 3D gizmo bones shader. + +shader_type spatial; +render_mode unshaded, shadows_disabled; +void vertex() { + if (!OUTPUT_IS_SRGB) { + COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); + } + VERTEX = VERTEX; + POSITION=PROJECTION_MATRIX*INV_CAMERA_MATRIX*WORLD_MATRIX*vec4(VERTEX.xyz,1.0); + POSITION.z = mix(POSITION.z, 0, 0.998); +} +void fragment() { + ALBEDO = COLOR.rgb; + ALPHA = COLOR.a; +} +)"); + selected_mat->set_shader(selected_sh); + + // Regist properties in editor settings. + EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4)); + EDITOR_DEF("editors/3d_gizmos/gizmo_colors/selected_bone", Color(0.8, 0.3, 0.0)); + EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_axis_length", (float)0.1); + EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_shape", 1); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/3d_gizmos/gizmo_settings/bone_shape", PROPERTY_HINT_ENUM, "Wire,Octahedron")); +} + +bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to<Skeleton3D>(p_spatial) != nullptr; +} + +String Skeleton3DGizmoPlugin::get_gizmo_name() const { + return "Skeleton3D"; +} + +int Skeleton3DGizmoPlugin::get_priority() const { + return -1; +} + +int Skeleton3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + ERR_FAIL_COND_V(!skeleton, -1); + + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + + if (!se->is_edit_mode()) { + return -1; + } + + if (Node3DEditor::get_singleton()->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) { + return -1; + } + + // Select bone. + real_t grab_threshold = 4 * EDSCALE; + Vector3 ray_from = p_camera->get_global_transform().origin; + Transform3D gt = skeleton->get_global_transform(); + int closest_idx = -1; + real_t closest_dist = 1e10; + const int bone_len = skeleton->get_bone_count(); + for (int i = 0; i < bone_len; i++) { + Vector3 joint_pos_3d = gt.xform(skeleton->get_bone_global_pose(i).origin); + Vector2 joint_pos_2d = p_camera->unproject_position(joint_pos_3d); + real_t dist_3d = ray_from.distance_to(joint_pos_3d); + real_t dist_2d = p_point.distance_to(joint_pos_2d); + if (dist_2d < grab_threshold && dist_3d < closest_dist) { + closest_dist = dist_3d; + closest_idx = i; + } + } + + if (closest_idx >= 0) { + WARN_PRINT("ray:"); + WARN_PRINT(itos(closest_idx)); + se->select_bone(closest_idx); + return closest_idx; + } + + se->select_bone(-1); + return -1; +} + +Transform3D Skeleton3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + ERR_FAIL_COND_V(!skeleton, Transform3D()); + + return skeleton->get_bone_global_pose(p_id); +} + +void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + ERR_FAIL_COND(!skeleton); + + // Prepare for global to local. + Transform3D original_to_local = Transform3D(); + int parent_idx = skeleton->get_bone_parent(p_id); + if (parent_idx >= 0) { + original_to_local = original_to_local * skeleton->get_bone_global_pose(parent_idx); + } + original_to_local = original_to_local * skeleton->get_bone_rest(p_id) * skeleton->get_bone_custom_pose(p_id); + Basis to_local = original_to_local.get_basis().inverse(); + + // Prepare transform. + Transform3D t = Transform3D(); + + // Basis. + t.basis = to_local * p_transform.get_basis(); + + // Origin. + Vector3 orig = Vector3(); + orig = skeleton->get_bone_pose(p_id).origin; + Vector3 sub = p_transform.origin - skeleton->get_bone_global_pose(p_id).origin; + t.origin = orig + to_local.xform(sub); + + // Apply transform. + skeleton->set_bone_pose(p_id, t); +} + +void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + ERR_FAIL_COND(!skeleton); + + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + for (int i = 0; i < p_ids.size(); i++) { + ur->create_action(TTR("Set Bone Transform")); + ur->add_do_method(skeleton, "set_bone_pose", p_ids[i], skeleton->get_bone_pose(p_ids[i])); + ur->add_undo_method(skeleton, "set_bone_pose", p_ids[i], se->get_bone_original()); + } + ur->commit_action(); +} + +void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + p_gizmo->clear(); + + int selected = -1; + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + if (se) { + selected = se->get_selected_bone(); + } + + Color bone_color = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_colors/skeleton"); + Color selected_bone_color = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_colors/selected_bone"); + real_t bone_axis_length = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_settings/bone_axis_length"); + int bone_shape = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_settings/bone_shape"); + + LocalVector<Color> axis_colors; + axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_x_color"), SNAME("Editor"))); + axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_y_color"), SNAME("Editor"))); + axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_z_color"), SNAME("Editor"))); + + Ref<SurfaceTool> surface_tool(memnew(SurfaceTool)); + surface_tool->begin(Mesh::PRIMITIVE_LINES); + + if (p_gizmo->is_selected()) { + surface_tool->set_material(selected_mat); + } else { + unselected_mat->set_albedo(bone_color); + surface_tool->set_material(unselected_mat); + } + + Vector<Transform3D> grests; + grests.resize(skeleton->get_bone_count()); + + LocalVector<int> bones; + LocalVector<float> weights; + bones.resize(4); + weights.resize(4); + for (int i = 0; i < 4; i++) { + bones[i] = 0; + weights[i] = 0; + } + weights[0] = 1; + + int current_bone_index = 0; + Vector<int> bones_to_process = skeleton->get_parentless_bones(); + + while (bones_to_process.size() > current_bone_index) { + int current_bone_idx = bones_to_process[current_bone_index]; + current_bone_index++; + + Color current_bone_color = (current_bone_idx == selected) ? selected_bone_color : bone_color; + + Vector<int> child_bones_vector; + child_bones_vector = skeleton->get_bone_children(current_bone_idx); + int child_bones_size = child_bones_vector.size(); + + // You have children but no parent, then you must be a root/parentless bone. + if (skeleton->get_bone_parent(current_bone_idx) < 0) { + grests.write[current_bone_idx] = skeleton->get_bone_rest(current_bone_idx); + } + + for (int i = 0; i < child_bones_size; i++) { + // Something wrong. + if (child_bones_vector[i] < 0) { + continue; + } + + int child_bone_idx = child_bones_vector[i]; + + grests.write[child_bone_idx] = grests[current_bone_idx] * skeleton->get_bone_rest(child_bone_idx); + + Vector3 v0 = grests[current_bone_idx].origin; + Vector3 v1 = grests[child_bone_idx].origin; + Vector3 d = (v1 - v0).normalized(); + real_t dist = v0.distance_to(v1); + + // Find closest axis. + int closest = -1; + real_t closest_d = 0.0; + for (int j = 0; j < 3; j++) { + real_t dp = Math::abs(grests[current_bone_idx].basis[j].normalized().dot(d)); + if (j == 0 || dp > closest_d) { + closest = j; + } + } + + // Draw bone. + switch (bone_shape) { + case 0: { // Wire shape. + surface_tool->set_color(current_bone_color); + bones[0] = current_bone_idx; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v0); + bones[0] = child_bone_idx; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v1); + } break; + + case 1: { // Octahedron shape. + Vector3 first; + Vector3 points[6]; + int point_idx = 0; + for (int j = 0; j < 3; j++) { + Vector3 axis; + if (first == Vector3()) { + axis = d.cross(d.cross(grests[current_bone_idx].basis[j])).normalized(); + first = axis; + } else { + axis = d.cross(first).normalized(); + } + + surface_tool->set_color(current_bone_color); + for (int k = 0; k < 2; k++) { + if (k == 1) { + axis = -axis; + } + Vector3 point = v0 + d * dist * 0.2; + point += axis * dist * 0.1; + + bones[0] = current_bone_idx; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v0); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(point); + + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(point); + bones[0] = child_bone_idx; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v1); + points[point_idx++] = point; + } + } + surface_tool->set_color(current_bone_color); + SWAP(points[1], points[2]); + bones[0] = current_bone_idx; + for (int j = 0; j < 6; j++) { + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(points[j]); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(points[(j + 1) % 6]); + } + } break; + } + + // Axis as root of the bone. + for (int j = 0; j < 3; j++) { + bones[0] = current_bone_idx; + surface_tool->set_color(axis_colors[j]); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v0); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v0 + (grests[current_bone_idx].basis.inverse())[j].normalized() * dist * bone_axis_length); + + if (j == closest) { + continue; + } + } + + // Axis at the end of the bone children. + if (i == child_bones_size - 1) { + for (int j = 0; j < 3; j++) { + bones[0] = child_bone_idx; + surface_tool->set_color(axis_colors[j]); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v1); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v1 + (grests[child_bone_idx].basis.inverse())[j].normalized() * dist * bone_axis_length); + + if (j == closest) { + continue; + } + } + } + + // Add the bone's children to the list of bones to be processed. + bones_to_process.push_back(child_bones_vector[i]); + } + } + + Ref<ArrayMesh> m = surface_tool->commit(); + p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(Ref<Skin>())); } diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h index 9de52c6fa8..e2a1d9a628 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.h +++ b/editor/plugins/skeleton_3d_editor_plugin.h @@ -33,7 +33,12 @@ #include "editor/editor_node.h" #include "editor/editor_plugin.h" +#include "editor/editor_properties.h" +#include "node_3d_editor_plugin.h" +#include "scene/3d/camera_3d.h" +#include "scene/3d/mesh_instance_3d.h" #include "scene/3d/skeleton_3d.h" +#include "scene/resources/immediate_mesh.h" class EditorInspectorPluginSkeleton; class Joint; @@ -41,8 +46,6 @@ class PhysicalBone3D; class Skeleton3DEditorPlugin; class Button; class CheckBox; -class EditorPropertyTransform3D; -class EditorPropertyVector3; class BoneTransformEditor : public VBoxContainer { GDCLASS(BoneTransformEditor, VBoxContainer); @@ -81,6 +84,8 @@ class BoneTransformEditor : public VBoxContainer { void _value_changed_transform(const String p_property_name, const Transform3D p_transform, const StringName p_edited_property_name, const bool p_boolean); // Changes the transform to the given transform and updates the UI accordingly. void _change_transform(Transform3D p_new_transform); + // Update it is truely keyable then. + void _update_key_button(const bool p_keyable); // Creates a Transform using the EditorPropertyVector3 properties. Transform3D compute_transform_from_vector3s() const; @@ -92,7 +97,7 @@ protected: public: BoneTransformEditor(Skeleton3D *p_skeleton); - // Which transform target to modify + // Which transform target to modify. void set_target(const String &p_prop); void set_label(const String &p_label) { label = p_label; } @@ -100,20 +105,21 @@ public: void _update_custom_pose_properties(); void _update_transform_properties(Transform3D 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 + // 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 + // When rest mode, pose and custom_pose editor are diasbled. + void set_properties_read_only(const bool p_readonly); + void set_transform_read_only(const bool p_readonly); + + // 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 + // Key Transform Button pressed. void _key_button_pressed(); - // Bone Enabled Checkbox toggled - void _checkbox_toggled(const bool p_toggled); + // Bone Enabled Checkbox toggled. + void _checkbox_pressed(); }; class Skeleton3DEditor : public VBoxContainer { @@ -121,13 +127,20 @@ class Skeleton3DEditor : public VBoxContainer { friend class Skeleton3DEditorPlugin; - enum Menu { - MENU_OPTION_CREATE_PHYSICAL_SKELETON + enum SkeletonOption { + SKELETON_OPTION_INIT_POSE, + SKELETON_OPTION_INSERT_KEYS, + SKELETON_OPTION_INSERT_KEYS_EXISTED, + SKELETON_OPTION_CREATE_PHYSICAL_SKELETON + }; + + enum RestOption { + REST_OPTION_POSE_TO_REST }; struct BoneInfo { PhysicalBone3D *physical_bone = nullptr; - Transform3D relative_rest; // Relative to skeleton node + Transform3D relative_rest; // Relative to skeleton node. }; EditorNode *editor; @@ -140,13 +153,24 @@ class Skeleton3DEditor : public VBoxContainer { BoneTransformEditor *pose_editor = nullptr; BoneTransformEditor *custom_pose_editor = nullptr; - MenuButton *options = nullptr; + VSeparator *separator; + MenuButton *skeleton_options = nullptr; + MenuButton *rest_options = nullptr; + Button *edit_mode_button; + + bool edit_mode = false; + EditorFileDialog *file_dialog = nullptr; - UndoRedo *undo_redo = nullptr; + bool keyable; + + static Skeleton3DEditor *singleton; - void _on_click_option(int p_option); + void _on_click_skeleton_option(int p_skeleton_option); + void _on_click_rest_option(int p_rest_option); void _file_selected(const String &p_file); + TreeItem *_find(TreeItem *p_node, const NodePath &p_path); + void edit_mode_toggled(const bool pressed); EditorFileDialog *file_export_lib = nullptr; @@ -155,6 +179,10 @@ class Skeleton3DEditor : public VBoxContainer { void create_editors(); + void init_pose(); + void insert_keys(bool p_all_bones); + void pose_to_rest(); + void create_physical_skeleton(); PhysicalBone3D *create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos); @@ -162,20 +190,56 @@ class Skeleton3DEditor : public VBoxContainer { 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); + void set_keyable(const bool p_keyable); + void set_rest_options_enabled(const bool p_rest_options_enabled); + + // Handle. + MeshInstance3D *handles_mesh_instance; + Ref<ImmediateMesh> handles_mesh; + Ref<ShaderMaterial> handle_material; + Ref<Shader> handle_shader; + + Transform3D bone_original; + + void _update_pose_enabled(int p_bone = -1); + void _update_show_rest_only(); + + void _update_gizmo_transform(); + void _update_gizmo_visible(); + + void _hide_handles(); + + void _draw_gizmo(); + void _draw_handles(); + + void _joint_tree_selection_changed(); + void _joint_tree_rmb_select(const Vector2 &p_pos); + void _update_properties(); + + void _subgizmo_selection_change(); + + int selected_bone = -1; + protected: void _notification(int p_what); void _node_removed(Node *p_node); static void _bind_methods(); public: + static Skeleton3DEditor *get_singleton() { return singleton; } + + void select_bone(int p_idx); + + int get_selected_bone() const; + void move_skeleton_bone(NodePath p_skeleton_path, int32_t p_selected_boneidx, int32_t p_target_boneidx); Skeleton3D *get_skeleton() const { return skeleton; }; - void _joint_tree_selection_changed(); - void _joint_tree_rmb_select(const Vector2 &p_pos); + bool is_edit_mode() const { return edit_mode; } - void _update_properties(); + void update_bone_original(); + Transform3D get_bone_original() { return bone_original; }; Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton3D *skeleton); ~Skeleton3DEditor(); @@ -186,6 +250,7 @@ class EditorInspectorPluginSkeleton : public EditorInspectorPlugin { friend class Skeleton3DEditorPlugin; + Skeleton3DEditor *skel_editor; EditorNode *editor; public: @@ -196,12 +261,40 @@ public: class Skeleton3DEditorPlugin : public EditorPlugin { GDCLASS(Skeleton3DEditorPlugin, EditorPlugin); + EditorInspectorPluginSkeleton *skeleton_plugin; EditorNode *editor; public: - Skeleton3DEditorPlugin(EditorNode *p_node); + virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; + + bool has_main_screen() const override { return false; } + virtual bool handles(Object *p_object) const override; virtual String get_name() const override { return "Skeleton3D"; } + + Skeleton3DEditorPlugin(EditorNode *p_node); +}; + +class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(Skeleton3DGizmoPlugin, EditorNode3DGizmoPlugin); + + Ref<StandardMaterial3D> unselected_mat; + Ref<ShaderMaterial> selected_mat; + Ref<Shader> selected_sh; + +public: + bool has_gizmo(Node3D *p_spatial) override; + String get_gizmo_name() const override; + int get_priority() const override; + + int subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const override; + Transform3D get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const override; + void set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) override; + void commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) override; + + void redraw(EditorNode3DGizmo *p_gizmo) override; + + Skeleton3DGizmoPlugin(); }; #endif // SKELETON_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index 06ba8a6168..1fc7eb98e0 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -407,10 +407,6 @@ void TextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) { code_editor->convert_case(p_case); } -void TextEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &TextEditor::add_syntax_highlighter); -} - static ScriptEditorBase *create_editor(const RES &p_resource) { if (Object::cast_to<TextFile>(*p_resource)) { return memnew(TextEditor); diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h index 9308fec210..7404557f46 100644 --- a/editor/plugins/text_editor.h +++ b/editor/plugins/text_editor.h @@ -87,8 +87,6 @@ private: }; protected: - static void _bind_methods(); - void _edit_option(int p_op); void _make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded, Vector2 p_position); void _text_edit_gui_input(const Ref<InputEvent> &ev); diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index cc5ff90541..c2e86f8b43 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -465,6 +465,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } + _fix_invalid_tiles_in_tile_map_selection(); } break; case DRAG_TYPE_BUCKET: { Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos)); @@ -483,6 +484,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } } } + _fix_invalid_tiles_in_tile_map_selection(); } break; default: break; @@ -508,6 +510,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p drag_start_mouse_pos = mpos; if (tile_map_selection.has(tile_map->world_to_map(drag_start_mouse_pos)) && !mb->is_shift_pressed()) { // Move the selection + _update_selection_pattern_from_tilemap_selection(); // Make sure the pattern is up to date before moving. drag_type = DRAG_TYPE_MOVE; drag_modified.clear(); for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { @@ -541,6 +544,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } + _fix_invalid_tiles_in_tile_map_selection(); } else if (tool_buttons_group->get_pressed_button() == line_tool_button) { drag_type = DRAG_TYPE_LINE; drag_start_mouse_pos = mpos; @@ -569,6 +573,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } } } + _fix_invalid_tiles_in_tile_map_selection(); } } } @@ -1323,6 +1328,25 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { } } +void TileMapEditorTilesPlugin::_fix_invalid_tiles_in_tile_map_selection() { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Set<Vector2i> to_remove; + for (Vector2i selected : tile_map_selection) { + TileMapCell cell = tile_map->get_cell(tile_map_layer, selected); + if (cell.source_id == TileSet::INVALID_SOURCE && cell.get_atlas_coords() == TileSetSource::INVALID_ATLAS_COORDS && cell.alternative_tile == TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) { + to_remove.insert(selected); + } + } + + for (Vector2i cell : to_remove) { + tile_map_selection.erase(cell); + } +} + void TileMapEditorTilesPlugin::_update_selection_pattern_from_tilemap_selection() { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { @@ -1423,6 +1447,8 @@ void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern( } } _update_bottom_panel(); + tile_atlas_control->update(); + alternative_tiles_control->update(); } void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h index 6126db59e9..a1ab3db318 100644 --- a/editor/plugins/tiles/tile_map_editor.h +++ b/editor/plugins/tiles/tile_map_editor.h @@ -124,6 +124,7 @@ private: void _update_selection_pattern_from_tileset_selection(); void _update_tileset_selection_from_selection_pattern(); void _update_fix_selected_and_hovered(); + void _fix_invalid_tiles_in_tile_map_selection(); ///// Bottom panel. ////. Label *missing_source_label; diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index bc026146ef..3fbd315aec 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -131,6 +131,7 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na return false; } + // ID and size related properties. if (tiles.size() == 1) { const Vector2i &coords = tiles.front()->get().tile; const int &alternative = tiles.front()->get().alternative; @@ -160,36 +161,6 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na tile_set_atlas_source->move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i); emit_signal(SNAME("changed"), "size_in_atlas"); return true; - } else if (p_name == "animation_columns") { - bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(coords, tile_set_atlas_source->get_tile_size_in_atlas(coords), p_value, tile_set_atlas_source->get_tile_animation_separation(coords), tile_set_atlas_source->get_tile_animation_frames_count(coords), coords); - ERR_FAIL_COND_V(!has_room_for_tile, false); - tile_set_atlas_source->set_tile_animation_columns(coords, p_value); - emit_signal(SNAME("changed"), "animation_columns"); - return true; - } else if (p_name == "animation_separation") { - bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(coords, tile_set_atlas_source->get_tile_size_in_atlas(coords), tile_set_atlas_source->get_tile_animation_columns(coords), p_value, tile_set_atlas_source->get_tile_animation_frames_count(coords), coords); - ERR_FAIL_COND_V(!has_room_for_tile, false); - tile_set_atlas_source->set_tile_animation_separation(coords, p_value); - emit_signal(SNAME("changed"), "animation_separation"); - return true; - } else if (p_name == "animation_speed") { - tile_set_atlas_source->set_tile_animation_speed(coords, p_value); - emit_signal(SNAME("changed"), "animation_speed"); - return true; - } else if (p_name == "animation_frames_count") { - bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(coords, tile_set_atlas_source->get_tile_size_in_atlas(coords), tile_set_atlas_source->get_tile_animation_columns(coords), tile_set_atlas_source->get_tile_animation_separation(coords), p_value, coords); - ERR_FAIL_COND_V(!has_room_for_tile, false); - tile_set_atlas_source->set_tile_animation_frames_count(coords, p_value); - notify_property_list_changed(); - emit_signal(SNAME("changed"), "animation_separation"); - return true; - } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) { - int frame = components[0].trim_prefix("animation_frame_").to_int(); - ERR_FAIL_INDEX_V(frame, tile_set_atlas_source->get_tile_animation_frames_count(coords), false); - if (components[1] == "duration") { - tile_set_atlas_source->set_tile_animation_frame_duration(coords, frame, p_value); - return true; - } } } else if (alternative > 0) { if (p_name == "alternative_id") { @@ -213,6 +184,74 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na } } + // Animation. + // Check if all tiles have an alternative_id of 0. + bool all_alternatve_id_zero = true; + for (TileSelection tile : tiles) { + if (tile.alternative != 0) { + all_alternatve_id_zero = false; + break; + } + } + + if (all_alternatve_id_zero) { + Vector<String> components = String(p_name).split("/", true, 2); + if (p_name == "animation_columns") { + for (TileSelection tile : tiles) { + bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), p_value, tile_set_atlas_source->get_tile_animation_separation(tile.tile), tile_set_atlas_source->get_tile_animation_frames_count(tile.tile), tile.tile); + if (!has_room_for_tile) { + ERR_PRINT("No room for tile"); + } else { + tile_set_atlas_source->set_tile_animation_columns(tile.tile, p_value); + } + } + emit_signal(SNAME("changed"), "animation_columns"); + return true; + } else if (p_name == "animation_separation") { + for (TileSelection tile : tiles) { + bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), tile_set_atlas_source->get_tile_animation_columns(tile.tile), p_value, tile_set_atlas_source->get_tile_animation_frames_count(tile.tile), tile.tile); + if (!has_room_for_tile) { + ERR_PRINT("No room for tile"); + } else { + tile_set_atlas_source->set_tile_animation_separation(tile.tile, p_value); + } + } + emit_signal(SNAME("changed"), "animation_separation"); + return true; + } else if (p_name == "animation_speed") { + for (TileSelection tile : tiles) { + tile_set_atlas_source->set_tile_animation_speed(tile.tile, p_value); + } + emit_signal(SNAME("changed"), "animation_speed"); + return true; + } else if (p_name == "animation_frames_count") { + for (TileSelection tile : tiles) { + bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), tile_set_atlas_source->get_tile_animation_columns(tile.tile), tile_set_atlas_source->get_tile_animation_separation(tile.tile), p_value, tile.tile); + if (!has_room_for_tile) { + ERR_PRINT("No room for tile"); + } else { + tile_set_atlas_source->set_tile_animation_frames_count(tile.tile, p_value); + } + } + notify_property_list_changed(); + emit_signal(SNAME("changed"), "animation_separation"); + return true; + } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) { + for (TileSelection tile : tiles) { + int frame = components[0].trim_prefix("animation_frame_").to_int(); + if (frame < 0 || frame >= tile_set_atlas_source->get_tile_animation_frames_count(tile.tile)) { + ERR_PRINT(vformat("No tile animation frame with index %d", frame)); + } else { + if (components[1] == "duration") { + tile_set_atlas_source->set_tile_animation_frame_duration(tile.tile, frame, p_value); + return true; + } + } + } + } + } + + // Other properties. bool any_valid = false; for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) { const Vector2i &coords = E->get().tile; @@ -238,6 +277,7 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_na return false; } + // ID and size related properties.s if (tiles.size() == 1) { const Vector2i &coords = tiles.front()->get().tile; const int &alternative = tiles.front()->get().alternative; @@ -250,27 +290,6 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_na } else if (p_name == "size_in_atlas") { r_ret = tile_set_atlas_source->get_tile_size_in_atlas(coords); return true; - } else if (p_name == "animation_columns") { - r_ret = tile_set_atlas_source->get_tile_animation_columns(coords); - return true; - } else if (p_name == "animation_separation") { - r_ret = tile_set_atlas_source->get_tile_animation_separation(coords); - return true; - } else if (p_name == "animation_speed") { - r_ret = tile_set_atlas_source->get_tile_animation_speed(coords); - return true; - } else if (p_name == "animation_frames_count") { - r_ret = tile_set_atlas_source->get_tile_animation_frames_count(coords); - return true; - } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) { - int frame = components[0].trim_prefix("animation_frame_").to_int(); - if (components[1] == "duration") { - if (frame < 0 || frame >= tile_set_atlas_source->get_tile_animation_frames_count(coords)) { - return false; - } - r_ret = tile_set_atlas_source->get_tile_animation_frame_duration(coords, frame); - return true; - } } } else if (alternative > 0) { if (p_name == "alternative_id") { @@ -280,6 +299,44 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_na } } + // Animation. + // Check if all tiles have an alternative_id of 0. + bool all_alternatve_id_zero = true; + for (TileSelection tile : tiles) { + if (tile.alternative != 0) { + all_alternatve_id_zero = false; + break; + } + } + + if (all_alternatve_id_zero) { + const Vector2i &coords = tiles.front()->get().tile; + + Vector<String> components = String(p_name).split("/", true, 2); + if (p_name == "animation_columns") { + r_ret = tile_set_atlas_source->get_tile_animation_columns(coords); + return true; + } else if (p_name == "animation_separation") { + r_ret = tile_set_atlas_source->get_tile_animation_separation(coords); + return true; + } else if (p_name == "animation_speed") { + r_ret = tile_set_atlas_source->get_tile_animation_speed(coords); + return true; + } else if (p_name == "animation_frames_count") { + r_ret = tile_set_atlas_source->get_tile_animation_frames_count(coords); + return true; + } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) { + int frame = components[0].trim_prefix("animation_frame_").to_int(); + if (components[1] == "duration") { + if (frame < 0 || frame >= tile_set_atlas_source->get_tile_animation_frames_count(coords)) { + return false; + } + r_ret = tile_set_atlas_source->get_tile_animation_frame_duration(coords, frame); + return true; + } + } + } + for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) { // Return the first tile with a property matching the name. // Note: It's a little bit annoying, but the behavior is the same the one in MultiNodeEdit. @@ -304,29 +361,42 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<Pro return; } + // ID and size related properties. if (tiles.size() == 1) { if (tiles.front()->get().alternative == 0) { p_list->push_back(PropertyInfo(Variant::VECTOR2I, "atlas_coords", PROPERTY_HINT_NONE, "")); p_list->push_back(PropertyInfo(Variant::VECTOR2I, "size_in_atlas", PROPERTY_HINT_NONE, "")); - - // Animation. - p_list->push_back(PropertyInfo(Variant::NIL, "Animation", PROPERTY_HINT_NONE, "animation_", PROPERTY_USAGE_GROUP)); - p_list->push_back(PropertyInfo(Variant::INT, "animation_columns", PROPERTY_HINT_NONE, "")); - p_list->push_back(PropertyInfo(Variant::VECTOR2I, "animation_separation", PROPERTY_HINT_NONE, "")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_speed", PROPERTY_HINT_NONE, "")); - p_list->push_back(PropertyInfo(Variant::INT, "animation_frames_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Frames,animation_frame_")); - if (tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile) == 1) { - p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_frame_0/duration", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY)); - } else { - for (int i = 0; i < tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile); i++) { - p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("animation_frame_%d/duration", i), PROPERTY_HINT_NONE, "")); - } - } } else { p_list->push_back(PropertyInfo(Variant::INT, "alternative_id", PROPERTY_HINT_NONE, "")); } } + // Animation. + // Check if all tiles have an alternative_id of 0. + bool all_alternatve_id_zero = true; + for (TileSelection tile : tiles) { + if (tile.alternative != 0) { + all_alternatve_id_zero = false; + break; + } + } + + if (all_alternatve_id_zero) { + p_list->push_back(PropertyInfo(Variant::NIL, "Animation", PROPERTY_HINT_NONE, "animation_", PROPERTY_USAGE_GROUP)); + p_list->push_back(PropertyInfo(Variant::INT, "animation_columns", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::VECTOR2I, "animation_separation", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_speed", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::INT, "animation_frames_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Frames,animation_frame_")); + // Not optimal, but returns value for the first tile. This is similar to what MultiNodeEdit does. + if (tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile) == 1) { + p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_frame_0/duration", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY)); + } else { + for (int i = 0; i < tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile); i++) { + p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("animation_frame_%d/duration", i), PROPERTY_HINT_NONE, "")); + } + } + } + // Get the list of properties common to all tiles (similar to what's done in MultiNodeEdit). struct PropertyId { int occurence_id = 0; diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index a5b607c0e8..4a59eb4fb3 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -1835,8 +1835,8 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V editor_data->get_undo_redo().add_do_method(this, "_set_owners", edited_scene, owners); - if (AnimationPlayerEditor::singleton->get_track_editor()->get_root() == node) { - editor_data->get_undo_redo().add_do_method(AnimationPlayerEditor::singleton->get_track_editor(), "set_root", node); + if (AnimationPlayerEditor::get_singleton()->get_track_editor()->get_root() == node) { + editor_data->get_undo_redo().add_do_method(AnimationPlayerEditor::get_singleton()->get_track_editor(), "set_root", node); } editor_data->get_undo_redo().add_undo_method(new_parent, "remove_child", node); @@ -1861,8 +1861,8 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V editor_data->get_undo_redo().add_undo_method(node->get_parent(), "add_child", node); editor_data->get_undo_redo().add_undo_method(node->get_parent(), "move_child", node, child_pos); editor_data->get_undo_redo().add_undo_method(this, "_set_owners", edited_scene, owners); - if (AnimationPlayerEditor::singleton->get_track_editor()->get_root() == node) { - editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::singleton->get_track_editor(), "set_root", node); + if (AnimationPlayerEditor::get_singleton()->get_track_editor()->get_root() == node) { + editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::get_singleton()->get_track_editor(), "set_root", node); } if (p_keep_global_xform) { @@ -2073,8 +2073,8 @@ void SceneTreeDock::_delete_confirm(bool p_cut) { editor_data->get_undo_redo().add_do_method(n->get_parent(), "remove_child", n); editor_data->get_undo_redo().add_undo_method(n->get_parent(), "add_child", n); editor_data->get_undo_redo().add_undo_method(n->get_parent(), "move_child", n, n->get_index()); - if (AnimationPlayerEditor::singleton->get_track_editor()->get_root() == n) { - editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::singleton->get_track_editor(), "set_root", n); + if (AnimationPlayerEditor::get_singleton()->get_track_editor()->get_root() == n) { + editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::get_singleton()->get_track_editor(), "set_root", n); } editor_data->get_undo_redo().add_undo_method(this, "_set_owners", edited_scene, owners); editor_data->get_undo_redo().add_undo_reference(n); diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index 8ffaf0829e..73523474ef 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -102,7 +102,7 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i undo_redo->commit_action(); } else if (p_id == BUTTON_PIN) { if (n->is_class("AnimationPlayer")) { - AnimationPlayerEditor::singleton->unpin(); + AnimationPlayerEditor::get_singleton()->unpin(); _update_tree(); } @@ -386,7 +386,7 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll _update_visibility_color(p_node, item); } else if (p_node->is_class("AnimationPlayer")) { - bool is_pinned = AnimationPlayerEditor::singleton->get_player() == p_node && AnimationPlayerEditor::singleton->is_pinned(); + bool is_pinned = AnimationPlayerEditor::get_singleton()->get_player() == p_node && AnimationPlayerEditor::get_singleton()->is_pinned(); if (is_pinned) { item->add_button(0, get_theme_icon(SNAME("Pin"), SNAME("EditorIcons")), BUTTON_PIN, false, TTR("AnimationPlayer is pinned.\nClick to unpin.")); @@ -527,7 +527,7 @@ void SceneTreeEditor::_node_removed(Node *p_node) { } void SceneTreeEditor::_node_renamed(Node *p_node) { - if (!get_scene_node()->is_ancestor_of(p_node)) { + if (p_node != get_scene_node() && !get_scene_node()->is_ancestor_of(p_node)) { return; } |