diff --git a/doc/classes/AnimationNodeBlendSpace1D.xml b/doc/classes/AnimationNodeBlendSpace1D.xml index 792eb652e25e..16f7f6a631b5 100644 --- a/doc/classes/AnimationNodeBlendSpace1D.xml +++ b/doc/classes/AnimationNodeBlendSpace1D.xml @@ -17,8 +17,17 @@ + - Adds a new point that represents a [param node] on the virtual axis at a given position set by [param pos]. You can insert it at a specific index using the [param at_index] argument. If you use the default value for [param at_index], the point is inserted at the end of the blend points array. + Adds a new point with [param name] that represents a [param node] on the virtual axis at a given position set by [param pos]. You can insert it at a specific index using the [param at_index] argument. If you use the default value for [param at_index], the point is inserted at the end of the blend points array. + [b]Note:[/b] If no name is provided, safe index is used as reference. In the future, empty names will be deprecated, so explicitly passing a name is recommended. + + + + + + + Returns the index of the blend point with the given [param name]. Returns [code]-1[/code] if no blend point with that name is found. @@ -27,6 +36,13 @@ Returns the number of points on the blend axis. + + + + + Returns the name of the blend point at index [param point]. + + @@ -48,6 +64,22 @@ Removes the point at index [param point] from the blend axis. + + + + + + Swaps the blend points at indices [param from_index] and [param to_index], exchanging their positions and properties. + + + + + + + + Sets the name of the blend point at index [param point]. If the name conflicts with an existing point, a unique name will be generated automatically. + + diff --git a/doc/classes/AnimationNodeBlendSpace2D.xml b/doc/classes/AnimationNodeBlendSpace2D.xml index 365887383a88..1b1f464c28f6 100644 --- a/doc/classes/AnimationNodeBlendSpace2D.xml +++ b/doc/classes/AnimationNodeBlendSpace2D.xml @@ -18,8 +18,10 @@ + - Adds a new point that represents a [param node] at the position set by [param pos]. You can insert it at a specific index using the [param at_index] argument. If you use the default value for [param at_index], the point is inserted at the end of the blend points array. + Adds a new point with [param name] that represents a [param node] at the position set by [param pos]. You can insert it at a specific index using the [param at_index] argument. If you use the default value for [param at_index], the point is inserted at the end of the blend points array. + [b]Note:[/b] If no name is provided, safe index is used as reference. In the future, empty names will be deprecated, so explicitly passing a name is recommended. @@ -32,12 +34,26 @@ Creates a new triangle using three points [param x], [param y], and [param z]. Triangles can overlap. You can insert the triangle at a specific index using the [param at_index] argument. If you use the default value for [param at_index], the point is inserted at the end of the blend points array. + + + + + Returns the index of the blend point with the given [param name]. Returns [code]-1[/code] if no blend point with that name is found. + + Returns the number of points in the blend space. + + + + + Returns the name of the blend point at index [param point]. + + @@ -80,6 +96,22 @@ Removes the triangle at index [param triangle] from the blend space. + + + + + + Swaps the blend points at indices [param from_index] and [param to_index], exchanging their positions and properties. + + + + + + + + Sets the name of the blend point at index [param point]. If the name conflicts with an existing point, a unique name will be generated automatically. + + diff --git a/editor/animation/animation_blend_space_1d_editor.cpp b/editor/animation/animation_blend_space_1d_editor.cpp index 225857e2aa64..7fc8d5dc60ea 100644 --- a/editor/animation/animation_blend_space_1d_editor.cpp +++ b/editor/animation/animation_blend_space_1d_editor.cpp @@ -46,6 +46,7 @@ #include "scene/gui/panel_container.h" #include "scene/gui/separator.h" #include "scene/gui/spin_box.h" +#include "scene/main/timer.h" StringName AnimationNodeBlendSpace1DEditor::get_blend_position_path() const { StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + "blend_position"; @@ -60,12 +61,20 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref k = p_event; - if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_keycode() == Key::KEY_DELETE && !k->is_echo()) { - if (selected_point != -1) { - if (!read_only) { - _erase_selected(); - } + if (k.is_valid() && k->is_pressed() && !k->is_echo()) { + if (k->get_keycode() == Key::ESCAPE && editing_point != -1) { + _cancel_inline_edit(); accept_event(); + return; + } + + if (tool_select->is_pressed() && k->get_keycode() == Key::KEY_DELETE) { + if (selected_point != -1) { + if (!read_only) { + _erase_selected(); + } + accept_event(); + } } } @@ -128,16 +137,26 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Refqueue_redraw(); // why not // try to see if a point can be selected - selected_point = -1; - _update_tool_erase(); + _set_selected_point(-1); + + // Check if clicking on text areas first. + for (int i = 0; i < text_rects.size(); i++) { + if (text_rects[i].has_point(mb->get_position())) { + _set_selected_point(i); + dragging_selected_attempt = true; + drag_from = mb->get_position(); + _update_edited_point_name(); + return; + } + } + + // Then check point positions. for (int i = 0; i < points.size(); i++) { if (Math::abs(float(points[i] - mb->get_position().x)) < 10 * EDSCALE) { - selected_point = i; + _set_selected_point(i); Ref node = blend_space->get_blend_point_node(i); - EditorNode::get_singleton()->push_item(node.ptr(), "", true); - if (mb->is_double_click() && AnimationTreeEditor::get_singleton()->can_edit(node)) { _open_editor(); return; @@ -145,19 +164,24 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Refget_position(); - _update_tool_erase(); - _update_edited_point_pos(); + _update_edited_point_name(); return; } } - - // If no point was selected, select host BlendSpace1D node. - if (selected_point == -1) { - EditorNode::get_singleton()->push_item(blend_space.ptr(), "", true); - } } if (mb.is_valid() && !mb->is_pressed() && dragging_selected_attempt && mb->get_button_index() == MouseButton::LEFT) { + // Check if releasing over text without any dragging - if so, start text editing instead. + if (!read_only && !dragging_selected) { + for (int i = 0; i < text_rects.size(); i++) { + if (text_rects[i].has_point(mb->get_position())) { + _start_inline_edit(i); + dragging_selected_attempt = false; + return; + } + } + } + if (!read_only) { if (dragging_selected) { // move @@ -170,7 +194,7 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Refcreate_action(TTR("Move Node Point")); + undo_redo->create_action(TTR("Move BlendSpace1D Node Point")); undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, point); undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point)); undo_redo->add_do_method(this, "_update_space"); @@ -221,6 +245,28 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Refqueue_redraw(); } + + // Handle mousewheel for reordering points. + Ref mw = p_event; + if (mw.is_valid() && mw->is_pressed() && (mw->get_button_index() == MouseButton::WHEEL_UP || mw->get_button_index() == MouseButton::WHEEL_DOWN)) { + if (!read_only && tool_select->is_pressed()) { + int hovered_point = -1; + for (int i = 0; i < points.size(); i++) { + if (Math::abs(float(points[i] - mw->get_position().x)) < 10 * EDSCALE) { + hovered_point = i; + break; + } + } + + if (hovered_point != -1) { + int direction = (mw->get_button_index() == MouseButton::WHEEL_DOWN) ? -1 : 1; + int new_index = hovered_point + direction; + _set_selected_point(hovered_point); + _edit_point_index(new_index); + accept_event(); + } + } + } } void AnimationNodeBlendSpace1DEditor::_blend_space_draw() { @@ -229,6 +275,9 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_draw() { return; } + // Clear text rectangles for fresh click detection. + text_rects.clear(); + Color linecolor = get_theme_color(SceneStringName(font_color), SNAME("Label")); Color linecolor_soft = linecolor; linecolor_soft.a *= 0.5; @@ -282,31 +331,33 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_draw() { for (int i = 0; i < blend_space->get_blend_point_count(); i++) { float point = blend_space->get_blend_point_position(i); - - if (!read_only) { - if (dragging_selected && selected_point == i) { - point += drag_ofs.x; - if (snap->is_pressed()) { - point = Math::snapped(point, blend_space->get_snap()); - } + if (!read_only && dragging_selected && selected_point == i) { + point += drag_ofs.x; + if (snap->is_pressed()) { + point = Math::snapped(point, blend_space->get_snap()); } } point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); point *= s.width; - points.push_back(point); - Vector2 gui_point = Vector2(point, s.height / 2.0); + Vector2 gui_point = (Vector2(point, s.height / 2.0) - icon->get_size() / 2.0).floor(); + blend_space_draw->draw_texture(i == selected_point ? icon_selected : icon, gui_point); - gui_point -= (icon->get_size() / 2.0); + if (point >= 0.0 && point <= s.width && editing_point != i) { + String name_text = show_indices ? itos(i) : String(blend_space->get_blend_point_name(i)); + Vector2 text_size = font->get_string_size(name_text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size); + Vector2 text_pos = Vector2(CLAMP(point - text_size.x / 2.0, 0, s.width - text_size.x), gui_point.y - 4 * EDSCALE); - gui_point = gui_point.floor(); + Color name_color = i == selected_point ? get_theme_color(SNAME("accent_color"), EditorStringName(Editor)) : linecolor; + blend_space_draw->draw_string(font, text_pos, name_text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, name_color); - if (i == selected_point) { - blend_space_draw->draw_texture(icon_selected, gui_point); - } else { - blend_space_draw->draw_texture(icon, gui_point); + if (text_rects.size() <= i) { + text_rects.resize(i + 1); + } + + text_rects.write[i] = Rect2(Vector2(text_pos.x, text_pos.y - font->get_ascent(font_size)), text_size); } } @@ -413,6 +464,19 @@ void AnimationNodeBlendSpace1DEditor::_file_opened(const String &p_file) { } } +String AnimationNodeBlendSpace1DEditor::_get_safe_name(const Ref &p_blend_space, const String &p_name) { + String final_name = p_name; + + // Append a number suffix if there's a naming conflict. + int suffix = 1; + while (p_blend_space->find_blend_point_by_name(final_name) != -1) { + suffix++; + final_name = p_name + " " + itos(suffix); + } + + return final_name; +} + void AnimationNodeBlendSpace1DEditor::_add_menu_type(int p_index) { Ref node; if (p_index == MENU_LOAD_FILE) { @@ -448,7 +512,7 @@ void AnimationNodeBlendSpace1DEditor::_add_menu_type(int p_index) { updating = true; EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Node Point")); - undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", node, add_point_pos); + undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", node, add_point_pos, -1, _get_safe_name(blend_space, node->get_class().replace_first("AnimationNode", ""))); undo_redo->add_undo_method(blend_space.ptr(), "remove_blend_point", blend_space->get_blend_point_count()); undo_redo->add_do_method(this, "_update_space"); undo_redo->add_undo_method(this, "_update_space"); @@ -467,7 +531,7 @@ void AnimationNodeBlendSpace1DEditor::_add_animation_type(int p_index) { updating = true; EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Animation Point")); - undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", anim, add_point_pos); + undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", anim, add_point_pos, -1, _get_safe_name(blend_space, animations_to_add[p_index])); undo_redo->add_undo_method(blend_space.ptr(), "remove_blend_point", blend_space->get_blend_point_count()); undo_redo->add_do_method(this, "_update_space"); undo_redo->add_undo_method(this, "_update_space"); @@ -508,10 +572,19 @@ void AnimationNodeBlendSpace1DEditor::_update_edited_point_pos() { updating = true; edit_value->set_value(pos); + index_edit->set_max(blend_space->get_blend_point_count() - 1); + index_edit->set_value(selected_point); + index_edit->set_editable(blend_space->get_blend_point_count() > 1 && !read_only); updating = false; } } +void AnimationNodeBlendSpace1DEditor::_update_edited_point_name() { + if (updating) { + return; + } +} + void AnimationNodeBlendSpace1DEditor::_update_tool_erase() { bool point_valid = selected_point >= 0 && selected_point < blend_space->get_blend_point_count(); tool_erase->set_disabled(!point_valid || read_only); @@ -521,8 +594,10 @@ void AnimationNodeBlendSpace1DEditor::_update_tool_erase() { if (AnimationTreeEditor::get_singleton()->can_edit(an)) { open_editor->show(); + open_editor_sep->show(); } else { open_editor->hide(); + open_editor_sep->hide(); } if (!read_only) { @@ -539,19 +614,21 @@ void AnimationNodeBlendSpace1DEditor::_erase_selected() { if (selected_point != -1) { updating = true; + Ref node = blend_space->get_blend_point_node(selected_point); + float position = blend_space->get_blend_point_position(selected_point); + String point_name = blend_space->get_blend_point_name(selected_point); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Remove BlendSpace1D Point")); undo_redo->add_do_method(blend_space.ptr(), "remove_blend_point", selected_point); - undo_redo->add_undo_method(blend_space.ptr(), "add_blend_point", blend_space->get_blend_point_node(selected_point), blend_space->get_blend_point_position(selected_point), selected_point); + undo_redo->add_undo_method(blend_space.ptr(), "add_blend_point", node, position, selected_point, point_name); undo_redo->add_do_method(this, "_update_space"); undo_redo->add_undo_method(this, "_update_space"); undo_redo->commit_action(); - // Return selection to host BlendSpace1D node. - EditorNode::get_singleton()->push_item(blend_space.ptr(), "", true); + _set_selected_point(-1); updating = false; - _update_tool_erase(); blend_space_draw->queue_redraw(); } @@ -577,11 +654,77 @@ void AnimationNodeBlendSpace1DEditor::_edit_point_pos(double) { blend_space_draw->queue_redraw(); } +void AnimationNodeBlendSpace1DEditor::_edit_point_index(double p_index) { + if (updating || selected_point < 0 || selected_point >= blend_space->get_blend_point_count()) { + return; + } + + int new_index = (int)p_index; + if (new_index < 0 || new_index >= blend_space->get_blend_point_count() || new_index == selected_point) { + return; + } + + updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Reorder BlendSpace1D Point Index")); + undo_redo->add_do_method(blend_space.ptr(), "reorder_blend_point", selected_point, new_index); + undo_redo->add_undo_method(blend_space.ptr(), "reorder_blend_point", new_index, selected_point); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->add_do_method(this, "_set_selected_point", new_index); + undo_redo->add_undo_method(this, "_set_selected_point", selected_point); + undo_redo->add_do_method(this, "_show_indices_with_cooldown"); + undo_redo->add_undo_method(this, "_show_indices_with_cooldown"); + undo_redo->commit_action(); + updating = false; + + blend_space_draw->queue_redraw(); +} + +void AnimationNodeBlendSpace1DEditor::_set_selected_point(int p_index) { + selected_point = p_index; + _update_tool_erase(); + if (p_index != -1) { + _update_edited_point_pos(); + Ref node = blend_space->get_blend_point_node(p_index); + EditorNode::get_singleton()->push_item(node.ptr(), "", true); + } else { + // Return selection to host BlendSpace1D node. + EditorNode::get_singleton()->push_item(blend_space.ptr(), "", true); + } +} + +void AnimationNodeBlendSpace1DEditor::_edit_point_name(const String &p_name) { + if (updating || selected_point == -1 || p_name.is_empty()) { + return; + } + + String old_name = blend_space->get_blend_point_name(selected_point); + if (p_name == old_name) { + return; + } + String safe_name = _get_safe_name(blend_space, p_name); + + updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Change BlendSpace1D Point Name")); + undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_name", selected_point, safe_name); + undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_name", selected_point, old_name); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->add_do_method(this, "_update_edited_point_name"); + undo_redo->add_undo_method(this, "_update_edited_point_name"); + undo_redo->commit_action(); + updating = false; + + blend_space_draw->queue_redraw(); +} + void AnimationNodeBlendSpace1DEditor::_open_editor() { if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) { Ref an = blend_space->get_blend_point_node(selected_point); ERR_FAIL_COND(an.is_null()); - AnimationTreeEditor::get_singleton()->enter_editor(itos(selected_point)); + AnimationTreeEditor::get_singleton()->enter_editor(blend_space->get_blend_point_name(selected_point)); } } @@ -634,6 +777,9 @@ void AnimationNodeBlendSpace1DEditor::_bind_methods() { ClassDB::bind_method("_update_tool_erase", &AnimationNodeBlendSpace1DEditor::_update_tool_erase); ClassDB::bind_method("_update_edited_point_pos", &AnimationNodeBlendSpace1DEditor::_update_edited_point_pos); + ClassDB::bind_method("_update_edited_point_name", &AnimationNodeBlendSpace1DEditor::_update_edited_point_name); + ClassDB::bind_method("_set_selected_point", &AnimationNodeBlendSpace1DEditor::_set_selected_point); + ClassDB::bind_method("_show_indices_with_cooldown", &AnimationNodeBlendSpace1DEditor::_show_indices_with_cooldown); } bool AnimationNodeBlendSpace1DEditor::can_edit(const Ref &p_node) { @@ -653,6 +799,7 @@ void AnimationNodeBlendSpace1DEditor::edit(const Ref &p_node) { tool_create->set_disabled(read_only); edit_value->set_editable(!read_only); + index_edit->set_editable(!read_only); label_value->set_editable(!read_only); min_value->set_editable(!read_only); max_value->set_editable(!read_only); @@ -660,6 +807,133 @@ void AnimationNodeBlendSpace1DEditor::edit(const Ref &p_node) { interpolation->set_disabled(read_only); } +void AnimationNodeBlendSpace1DEditor::_start_inline_edit(int p_point) { + if (editing_point != -1 || p_point < 0 || p_point >= blend_space->get_blend_point_count()) { + return; + } + + editing_point = p_point; + _set_selected_point(p_point); + + inline_editor = memnew(LineEdit); + blend_space_draw->add_child(inline_editor); + + inline_editor->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("accent_color"), EditorStringName(Editor))); + inline_editor->add_theme_color_override("font_selected_color", Color::named("white")); + inline_editor->add_theme_color_override("selection_color", get_theme_color(SNAME("accent_color"), EditorStringName(Editor))); + StyleBoxEmpty *empty_style = memnew(StyleBoxEmpty); + empty_style->set_content_margin_all(0); + inline_editor->add_theme_style_override(CoreStringName(normal), empty_style); + inline_editor->add_theme_style_override("focus", memnew(StyleBoxEmpty)); + inline_editor->add_theme_style_override("read_only", memnew(StyleBoxEmpty)); + inline_editor->add_theme_constant_override("minimum_character_width", 0); + inline_editor->set_flat(true); + + inline_editor->set_text(blend_space->get_blend_point_name(p_point)); + inline_editor->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + inline_editor->set_expand_to_text_length_enabled(true); + + if (p_point < text_rects.size() && p_point < points.size()) { + Rect2 text_rect = text_rects[p_point]; + + inline_editor_point_x = points[p_point]; + + float editor_width = text_rect.size.x; + inline_editor->set_size(Vector2(editor_width, text_rect.size.y)); + + Size2 s = blend_space_draw->get_size(); + + float editor_x = inline_editor_point_x - editor_width / 2.0; + editor_x = CLAMP(editor_x, 0.0f, s.width - editor_width); + inline_editor->set_position(Vector2(editor_x, text_rect.position.y - 1 * EDSCALE)); + } + + inline_editor->connect(SceneStringName(text_changed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_inline_editor_text_changed)); + inline_editor->connect(SceneStringName(text_submitted), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_finish_inline_edit_with_text)); + inline_editor->connect(SceneStringName(focus_exited), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_finish_inline_edit)); + + inline_editor->grab_focus(); + inline_editor->select_all(); + + blend_space_draw->queue_redraw(); +} + +void AnimationNodeBlendSpace1DEditor::_finish_inline_edit() { + if (editing_point == -1 || !inline_editor) { + return; + } + + String new_name = inline_editor->get_text(); + _edit_point_name(new_name); + + _cancel_inline_edit(); +} + +void AnimationNodeBlendSpace1DEditor::_finish_inline_edit_with_text(const String &p_text) { + if (editing_point == -1 || !inline_editor) { + return; + } + + _edit_point_name(p_text); + + _cancel_inline_edit(); +} + +void AnimationNodeBlendSpace1DEditor::_cancel_inline_edit() { + if (inline_editor) { + inline_editor->queue_free(); + inline_editor = nullptr; + } + editing_point = -1; + blend_space_draw->queue_redraw(); +} + +void AnimationNodeBlendSpace1DEditor::_inline_editor_text_changed(const String &p_text) { + if (!inline_editor) { + return; + } + + inline_editor->set_size(Vector2(0, inline_editor->get_size().y)); + + Vector2 editor_size = inline_editor->get_size(); + Size2 s = blend_space_draw->get_size(); + + float editor_x = inline_editor_point_x - editor_size.x / 2.0; + editor_x = CLAMP(editor_x, 0.0f, s.width - editor_size.x); + + inline_editor->set_position(Vector2(editor_x, inline_editor->get_position().y)); +} + +void AnimationNodeBlendSpace1DEditor::_index_edit_focus_entered() { + if (index_focus_cooldown_timer->is_stopped() == false) { + index_focus_cooldown_timer->stop(); + } + index_edit_has_focus = true; + show_indices = true; + blend_space_draw->queue_redraw(); +} + +void AnimationNodeBlendSpace1DEditor::_index_edit_focus_exited() { + index_edit_has_focus = false; + index_focus_cooldown_timer->start(); +} + +void AnimationNodeBlendSpace1DEditor::_index_focus_cooldown_timeout() { + if (!index_edit_has_focus) { + show_indices = false; + blend_space_draw->queue_redraw(); + } +} + +void AnimationNodeBlendSpace1DEditor::_show_indices_with_cooldown() { + if (index_focus_cooldown_timer->is_stopped() == false) { + index_focus_cooldown_timer->stop(); + } + show_indices = true; + index_focus_cooldown_timer->start(); + blend_space_draw->queue_redraw(); +} + AnimationNodeBlendSpace1DEditor *AnimationNodeBlendSpace1DEditor::singleton = nullptr; AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { @@ -677,7 +951,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { tool_select->set_button_group(bg); top_hb->add_child(tool_select); tool_select->set_pressed(true); - tool_select->set_tooltip_text(TTR("Select and move points.\nRMB: Create point at position clicked.\nShift+LMB+Drag: Set the blending position within the space.")); + tool_select->set_tooltip_text(TTR("Select and move points.\nRMB: Create point at position clicked.\nShift+LMB+Drag: Set the blending position within the space.\nScroll: Increment or decrement index.")); tool_select->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_tool_switch).bind(0)); tool_create = memnew(Button); @@ -722,23 +996,46 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { snap_value->set_accessibility_name(TTRC("Grid Step")); top_hb->add_child(memnew(VSeparator)); - top_hb->add_child(memnew(Label(TTR("Sync:")))); + top_hb->add_child(memnew(Label(TTR("Sync")))); sync = memnew(CheckBox); top_hb->add_child(sync); sync->connect(SceneStringName(toggled), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed)); top_hb->add_child(memnew(VSeparator)); - top_hb->add_child(memnew(Label(TTR("Blend:")))); + top_hb->add_child(memnew(Label(TTR("Blend")))); interpolation = memnew(OptionButton); top_hb->add_child(interpolation); interpolation->connect(SceneStringName(item_selected), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed)); + top_hb->add_spacer(); + edit_hb = memnew(HBoxContainer); top_hb->add_child(edit_hb); + + open_editor = memnew(Button); + edit_hb->add_child(open_editor); + open_editor->set_text(TTR("Open Editor")); + open_editor->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_open_editor), CONNECT_DEFERRED); + open_editor_sep = memnew(VSeparator); + edit_hb->add_child(open_editor_sep); + + edit_hb->add_child(memnew(Label(TTR("Index")))); + index_edit = memnew(SpinBox); + edit_hb->add_child(index_edit); + index_edit->set_min(0); + index_edit->set_step(1); + index_edit->set_allow_greater(false); + index_edit->set_allow_lesser(false); + index_edit->set_accessibility_name(TTRC("Blend Point Index")); + index_edit->set_tooltip_text(TTR("Index of the blend point.\nValues outside of the valid range will be clamped to the nearest index.")); + index_edit->connect(SceneStringName(value_changed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_edit_point_index)); + index_edit->get_line_edit()->connect(SceneStringName(focus_entered), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_index_edit_focus_entered)); + index_edit->get_line_edit()->connect(SceneStringName(focus_exited), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_index_edit_focus_exited)); + edit_hb->add_child(memnew(VSeparator)); - edit_hb->add_child(memnew(Label(TTR("Point")))); + edit_hb->add_child(memnew(Label(TTR("Position")))); edit_value = memnew(SpinBox); edit_hb->add_child(edit_value); edit_value->set_min(-1000); @@ -747,13 +1044,9 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { edit_value->set_accessibility_name(TTRC("Blend Value")); edit_value->connect(SceneStringName(value_changed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_edit_point_pos)); - open_editor = memnew(Button); - edit_hb->add_child(open_editor); - open_editor->set_text(TTR("Open Editor")); - open_editor->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_open_editor), CONNECT_DEFERRED); - edit_hb->hide(); open_editor->hide(); + open_editor_sep->hide(); VBoxContainer *main_vb = memnew(VBoxContainer); add_child(main_vb); @@ -830,5 +1123,12 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { open_file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); open_file->connect("file_selected", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_file_opened)); + // Create timer for index focus cooldown (1.5 seconds). + index_focus_cooldown_timer = memnew(Timer); + add_child(index_focus_cooldown_timer); + index_focus_cooldown_timer->set_wait_time(1.5); + index_focus_cooldown_timer->set_one_shot(true); + index_focus_cooldown_timer->connect("timeout", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_index_focus_cooldown_timeout)); + set_custom_minimum_size(Size2(0, 150 * EDSCALE)); } diff --git a/editor/animation/animation_blend_space_1d_editor.h b/editor/animation/animation_blend_space_1d_editor.h index 6b7778d0bc6d..899bd8ffa523 100644 --- a/editor/animation/animation_blend_space_1d_editor.h +++ b/editor/animation/animation_blend_space_1d_editor.h @@ -41,6 +41,7 @@ class LineEdit; class OptionButton; class PanelContainer; class SpinBox; +class Timer; class VSeparator; class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin { @@ -68,6 +69,8 @@ class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin { HBoxContainer *edit_hb = nullptr; SpinBox *edit_value = nullptr; Button *open_editor = nullptr; + VSeparator *open_editor_sep = nullptr; + SpinBox *index_edit = nullptr; int selected_point = -1; @@ -101,15 +104,36 @@ class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin { Vector2 drag_from; Vector2 drag_ofs; + Vector text_rects; + int editing_point = -1; + LineEdit *inline_editor = nullptr; + float inline_editor_point_x = 0.0f; + bool index_edit_has_focus = false; + bool show_indices = false; + Timer *index_focus_cooldown_timer = nullptr; + void _add_menu_type(int p_index); void _add_animation_type(int p_index); void _tool_switch(int p_tool); void _update_edited_point_pos(); + void _update_edited_point_name(); void _update_tool_erase(); void _erase_selected(); void _edit_point_pos(double); + void _edit_point_name(const String &p_name); + void _edit_point_index(double p_index); + void _set_selected_point(int p_index); + void _start_inline_edit(int p_point); + void _finish_inline_edit(); + void _finish_inline_edit_with_text(const String &p_text); + void _cancel_inline_edit(); + void _inline_editor_text_changed(const String &p_text); void _open_editor(); + void _index_edit_focus_entered(); + void _index_edit_focus_exited(); + void _index_focus_cooldown_timeout(); + void _show_indices_with_cooldown(); EditorFileDialog *open_file = nullptr; Ref file_loaded; @@ -122,6 +146,7 @@ class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin { }; StringName get_blend_position_path() const; + String _get_safe_name(const Ref &p_blend_space, const String &p_name); protected: void _notification(int p_what); @@ -129,6 +154,7 @@ class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin { public: static AnimationNodeBlendSpace1DEditor *get_singleton() { return singleton; } + void refresh_editor() { _update_space(); } virtual bool can_edit(const Ref &p_node) override; virtual void edit(const Ref &p_node) override; AnimationNodeBlendSpace1DEditor(); diff --git a/editor/animation/animation_blend_space_2d_editor.cpp b/editor/animation/animation_blend_space_2d_editor.cpp index aa9bb4e6bc9d..0c853b21719b 100644 --- a/editor/animation/animation_blend_space_2d_editor.cpp +++ b/editor/animation/animation_blend_space_2d_editor.cpp @@ -52,6 +52,7 @@ #include "scene/gui/panel_container.h" #include "scene/gui/separator.h" #include "scene/gui/spin_box.h" +#include "scene/main/timer.h" #include "scene/main/window.h" bool AnimationNodeBlendSpace2DEditor::can_edit(const Ref &p_node) { @@ -86,6 +87,7 @@ void AnimationNodeBlendSpace2DEditor::edit(const Ref &p_node) { label_y->set_editable(!read_only); edit_x->set_editable(!read_only); edit_y->set_editable(!read_only); + index_edit->set_editable(!read_only); tool_triangle->set_disabled(read_only); auto_triangles->set_disabled(read_only); sync->set_disabled(read_only); @@ -104,12 +106,20 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref k = p_event; - if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_keycode() == Key::KEY_DELETE && !k->is_echo()) { - if (selected_point != -1 || selected_triangle != -1) { - if (!read_only) { - _erase_selected(); - } + if (k.is_valid() && k->is_pressed() && !k->is_echo()) { + if (k->get_keycode() == Key::ESCAPE && editing_point != -1) { + _cancel_inline_edit(); accept_event(); + return; + } + + if (tool_select->is_pressed() && k->get_keycode() == Key::KEY_DELETE) { + if (selected_point != -1 || selected_triangle != -1) { + if (!read_only) { + _erase_selected(); + } + accept_event(); + } } } @@ -168,17 +178,28 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Refis_pressed() && tool_select->is_pressed() && !mb->is_shift_pressed() && !mb->is_command_or_control_pressed() && mb->get_button_index() == MouseButton::LEFT) { blend_space_draw->queue_redraw(); //update anyway + //try to see if a point can be selected - selected_point = -1; + _set_selected_point(-1); selected_triangle = -1; - _update_tool_erase(); + // Check if clicking on text areas first. + for (int i = 0; i < text_rects.size(); i++) { + if (text_rects[i].has_point(mb->get_position())) { + _set_selected_point(i); + dragging_selected_attempt = true; + drag_from = mb->get_position(); + _update_edited_point_name(); + return; + } + } + + // Then check point positions. for (int i = 0; i < points.size(); i++) { if (points[i].distance_to(mb->get_position()) < 10 * EDSCALE) { - selected_point = i; - Ref node = blend_space->get_blend_point_node(i); - EditorNode::get_singleton()->push_item(node.ptr(), "", true); + _set_selected_point(i); + Ref node = blend_space->get_blend_point_node(i); if (mb->is_double_click() && AnimationTreeEditor::get_singleton()->can_edit(node)) { _open_editor(); return; @@ -186,8 +207,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Refget_position(); - _update_tool_erase(); - _update_edited_point_pos(); + _update_edited_point_name(); return; } } @@ -210,17 +230,12 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Refpush_item(blend_space.ptr(), "", true); - } } if (mb.is_valid() && mb->is_pressed() && tool_triangle->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { blend_space_draw->queue_redraw(); //update anyway //try to see if a point can be selected - selected_point = -1; + _set_selected_point(-1); for (int i = 0; i < points.size(); i++) { if (making_triangle.has(i)) { @@ -254,6 +269,17 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Refis_pressed() && dragging_selected_attempt && mb->get_button_index() == MouseButton::LEFT) { + // Check if releasing over text without actual dragging - if so, start text editing instead. + if (!read_only && !dragging_selected) { + for (int i = 0; i < text_rects.size(); i++) { + if (text_rects[i].has_point(mb->get_position())) { + _start_inline_edit(i); + dragging_selected_attempt = false; + return; + } + } + } + if (dragging_selected) { //move Vector2 point = blend_space->get_blend_point_position(selected_point); @@ -265,7 +291,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Refcreate_action(TTR("Move Node Point")); + undo_redo->create_action(TTR("Move BlendSpace2D Node Point")); undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, point); undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point)); undo_redo->add_do_method(this, "_update_space"); @@ -327,6 +353,29 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Refqueue_redraw(); } + + // Handle mousewheel for reordering points. + Ref mw = p_event; + if (mw.is_valid() && mw->is_pressed() && (mw->get_button_index() == MouseButton::WHEEL_UP || mw->get_button_index() == MouseButton::WHEEL_DOWN)) { + if (!read_only && tool_select->is_pressed()) { + // Check if hovering over a point. + int hovered_point = -1; + for (int i = 0; i < points.size(); i++) { + if (points[i].distance_to(mw->get_position()) < 10 * EDSCALE) { + hovered_point = i; + break; + } + } + + if (hovered_point != -1) { + int direction = (mw->get_button_index() == MouseButton::WHEEL_DOWN) ? -1 : 1; + int new_index = hovered_point + direction; + _set_selected_point(hovered_point); + _edit_point_index(new_index); + accept_event(); + } + } + } } void AnimationNodeBlendSpace2DEditor::_file_opened(const String &p_file) { @@ -338,6 +387,19 @@ void AnimationNodeBlendSpace2DEditor::_file_opened(const String &p_file) { } } +String AnimationNodeBlendSpace2DEditor::_get_safe_name(const Ref &p_blend_space, const String &p_name) { + String final_name = p_name; + + // Append a number suffix if there's a naming conflict. + int suffix = 1; + while (p_blend_space->find_blend_point_by_name(final_name) != -1) { + suffix++; + final_name = p_name + " " + itos(suffix); + } + + return final_name; +} + void AnimationNodeBlendSpace2DEditor::_add_menu_type(int p_index) { Ref node; if (p_index == MENU_LOAD_FILE) { @@ -373,7 +435,7 @@ void AnimationNodeBlendSpace2DEditor::_add_menu_type(int p_index) { updating = true; EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Node Point")); - undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", node, add_point_pos); + undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", node, add_point_pos, -1, _get_safe_name(blend_space, node->get_class().replace_first("AnimationNode", ""))); undo_redo->add_undo_method(blend_space.ptr(), "remove_blend_point", blend_space->get_blend_point_count()); undo_redo->add_do_method(this, "_update_space"); undo_redo->add_undo_method(this, "_update_space"); @@ -392,7 +454,7 @@ void AnimationNodeBlendSpace2DEditor::_add_animation_type(int p_index) { updating = true; EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Animation Point")); - undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", anim, add_point_pos); + undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", anim, add_point_pos, -1, _get_safe_name(blend_space, animations_to_add[p_index])); undo_redo->add_undo_method(blend_space.ptr(), "remove_blend_point", blend_space->get_blend_point_count()); undo_redo->add_do_method(this, "_update_space"); undo_redo->add_undo_method(this, "_update_space"); @@ -411,8 +473,10 @@ void AnimationNodeBlendSpace2DEditor::_update_tool_erase() { Ref an = blend_space->get_blend_point_node(selected_point); if (AnimationTreeEditor::get_singleton()->can_edit(an)) { open_editor->show(); + open_editor_sep->show(); } else { open_editor->hide(); + open_editor_sep->hide(); } if (!read_only) { edit_hb->show(); @@ -455,6 +519,9 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { return; } + // Clear text rectangles for fresh click detection. + text_rects.clear(); + Color linecolor = get_theme_color(SceneStringName(font_color), SNAME("Label")); Color linecolor_soft = linecolor; linecolor_soft.a *= 0.5; @@ -563,26 +630,39 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { points.clear(); for (int i = 0; i < blend_space->get_blend_point_count(); i++) { Vector2 point = blend_space->get_blend_point_position(i); - if (!read_only) { - if (dragging_selected && selected_point == i) { - point += drag_ofs; - if (snap->is_pressed()) { - point = point.snapped(blend_space->get_snap()); - } + if (!read_only && dragging_selected && selected_point == i) { + point += drag_ofs; + if (snap->is_pressed()) { + point = point.snapped(blend_space->get_snap()); } } + point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); point *= s; point.y = s.height - point.y; - points.push_back(point); - point -= (icon->get_size() / 2); - point = point.floor(); - if (i == selected_point) { - blend_space_draw->draw_texture(icon_selected, point); - } else { - blend_space_draw->draw_texture(icon, point); + Vector2 gui_point = (point - icon->get_size() / 2).floor(); + blend_space_draw->draw_texture(i == selected_point ? icon_selected : icon, gui_point); + + if (point.x >= 0.0 && point.x <= s.width && point.y >= 0.0 && point.y <= s.height && editing_point != i) { + String name_text = show_indices ? itos(i) : String(blend_space->get_blend_point_name(i)); + Vector2 text_size = font->get_string_size(name_text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size); + + float half_icon_h = icon->get_size().y / 2.0; + float above_pos_y = point.y - half_icon_h - 4 * EDSCALE; + float text_x = CLAMP(point.x - text_size.x / 2.0, 0, s.width - text_size.x); + float text_y = above_pos_y >= text_size.y ? above_pos_y : point.y + half_icon_h + font->get_ascent(font_size); + Vector2 text_pos = Vector2(text_x, text_y); + + Color name_color = i == selected_point ? get_theme_color(SNAME("accent_color"), EditorStringName(Editor)) : linecolor; + blend_space_draw->draw_string(font, text_pos, name_text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, name_color); + + if (text_rects.size() <= i) { + text_rects.resize(i + 1); + } + + text_rects.write[i] = Rect2(Vector2(text_pos.x, text_pos.y - font->get_ascent(font_size)), text_size); } } @@ -727,9 +807,14 @@ void AnimationNodeBlendSpace2DEditor::_erase_selected() { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); if (selected_point != -1) { updating = true; + + Ref node = blend_space->get_blend_point_node(selected_point); + Vector2 position = blend_space->get_blend_point_position(selected_point); + String point_name = blend_space->get_blend_point_name(selected_point); + undo_redo->create_action(TTR("Remove BlendSpace2D Point")); undo_redo->add_do_method(blend_space.ptr(), "remove_blend_point", selected_point); - undo_redo->add_undo_method(blend_space.ptr(), "add_blend_point", blend_space->get_blend_point_node(selected_point), blend_space->get_blend_point_position(selected_point), selected_point); + undo_redo->add_undo_method(blend_space.ptr(), "add_blend_point", node, position, selected_point, point_name); //restore triangles using this point for (int i = 0; i < blend_space->get_triangle_count(); i++) { @@ -745,11 +830,9 @@ void AnimationNodeBlendSpace2DEditor::_erase_selected() { undo_redo->add_undo_method(this, "_update_space"); undo_redo->commit_action(); - // Return selection to host BlendSpace2D node. - EditorNode::get_singleton()->push_item(blend_space.ptr(), "", true); + _set_selected_point(-1); updating = false; - _update_tool_erase(); blend_space_draw->queue_redraw(); } else if (selected_triangle != -1) { @@ -787,17 +870,26 @@ void AnimationNodeBlendSpace2DEditor::_update_edited_point_pos() { updating = true; edit_x->set_value(pos.x); edit_y->set_value(pos.y); + index_edit->set_max(blend_space->get_blend_point_count() - 1); + index_edit->set_value(selected_point); + index_edit->set_editable(blend_space->get_blend_point_count() > 1 && !read_only); updating = false; } } +void AnimationNodeBlendSpace2DEditor::_update_edited_point_name() { + if (updating) { + return; + } +} + void AnimationNodeBlendSpace2DEditor::_edit_point_pos(double) { if (updating) { return; } updating = true; EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Move Node Point")); + undo_redo->create_action(TTR("Move BlendSpace2D Node Point")); undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, Vector2(edit_x->get_value(), edit_y->get_value())); undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point)); undo_redo->add_do_method(this, "_update_space"); @@ -810,6 +902,71 @@ void AnimationNodeBlendSpace2DEditor::_edit_point_pos(double) { blend_space_draw->queue_redraw(); } +void AnimationNodeBlendSpace2DEditor::_edit_point_index(double p_index) { + if (updating || selected_point < 0 || selected_point >= blend_space->get_blend_point_count()) { + return; + } + + int new_index = (int)p_index; + if (new_index < 0 || new_index >= blend_space->get_blend_point_count() || new_index == selected_point) { + return; + } + + updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Reorder BlendSpace2D Point Index")); + undo_redo->add_do_method(blend_space.ptr(), "reorder_blend_point", selected_point, new_index); + undo_redo->add_undo_method(blend_space.ptr(), "reorder_blend_point", new_index, selected_point); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->add_do_method(this, "_set_selected_point", new_index); + undo_redo->add_undo_method(this, "_set_selected_point", selected_point); + undo_redo->add_do_method(this, "_show_indices_with_cooldown"); + undo_redo->add_undo_method(this, "_show_indices_with_cooldown"); + undo_redo->commit_action(); + updating = false; + + blend_space_draw->queue_redraw(); +} + +void AnimationNodeBlendSpace2DEditor::_set_selected_point(int p_index) { + selected_point = p_index; + _update_tool_erase(); + if (p_index != -1) { + _update_edited_point_pos(); + Ref node = blend_space->get_blend_point_node(p_index); + EditorNode::get_singleton()->push_item(node.ptr(), "", true); + } else { + EditorNode::get_singleton()->push_item(blend_space.ptr(), "", true); + } +} + +void AnimationNodeBlendSpace2DEditor::_edit_point_name(const String &p_name) { + if (updating || selected_point == -1 || p_name.is_empty()) { + return; + } + + String old_name = blend_space->get_blend_point_name(selected_point); + if (p_name == old_name) { + return; + } + String safe_name = _get_safe_name(blend_space, p_name); + + updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Change BlendSpace2D Point Name")); + undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_name", selected_point, safe_name); + undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_name", selected_point, old_name); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->add_do_method(this, "_update_edited_point_name"); + undo_redo->add_undo_method(this, "_update_edited_point_name"); + undo_redo->commit_action(); + updating = false; + + blend_space_draw->queue_redraw(); +} + void AnimationNodeBlendSpace2DEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: { @@ -864,10 +1021,40 @@ void AnimationNodeBlendSpace2DEditor::_open_editor() { if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) { Ref an = blend_space->get_blend_point_node(selected_point); ERR_FAIL_COND(an.is_null()); - AnimationTreeEditor::get_singleton()->enter_editor(itos(selected_point)); + AnimationTreeEditor::get_singleton()->enter_editor(blend_space->get_blend_point_name(selected_point)); } } +void AnimationNodeBlendSpace2DEditor::_index_edit_focus_entered() { + if (index_focus_cooldown_timer->is_stopped() == false) { + index_focus_cooldown_timer->stop(); + } + index_edit_has_focus = true; + show_indices = true; + blend_space_draw->queue_redraw(); +} + +void AnimationNodeBlendSpace2DEditor::_index_edit_focus_exited() { + index_edit_has_focus = false; + index_focus_cooldown_timer->start(); +} + +void AnimationNodeBlendSpace2DEditor::_index_focus_cooldown_timeout() { + if (!index_edit_has_focus) { + show_indices = false; + blend_space_draw->queue_redraw(); + } +} + +void AnimationNodeBlendSpace2DEditor::_show_indices_with_cooldown() { + if (index_focus_cooldown_timer->is_stopped() == false) { + index_focus_cooldown_timer->stop(); + } + show_indices = true; + index_focus_cooldown_timer->start(); + blend_space_draw->queue_redraw(); +} + void AnimationNodeBlendSpace2DEditor::_auto_triangles_toggled() { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Toggle Auto Triangles")); @@ -883,6 +1070,106 @@ void AnimationNodeBlendSpace2DEditor::_bind_methods() { ClassDB::bind_method("_update_tool_erase", &AnimationNodeBlendSpace2DEditor::_update_tool_erase); ClassDB::bind_method("_update_edited_point_pos", &AnimationNodeBlendSpace2DEditor::_update_edited_point_pos); + ClassDB::bind_method("_update_edited_point_name", &AnimationNodeBlendSpace2DEditor::_update_edited_point_name); + ClassDB::bind_method("_set_selected_point", &AnimationNodeBlendSpace2DEditor::_set_selected_point); + ClassDB::bind_method("_show_indices_with_cooldown", &AnimationNodeBlendSpace2DEditor::_show_indices_with_cooldown); +} + +void AnimationNodeBlendSpace2DEditor::_start_inline_edit(int p_point) { + if (editing_point != -1 || p_point < 0 || p_point >= blend_space->get_blend_point_count()) { + return; + } + + editing_point = p_point; + _set_selected_point(p_point); + + inline_editor = memnew(LineEdit); + blend_space_draw->add_child(inline_editor); + + inline_editor->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("accent_color"), EditorStringName(Editor))); + inline_editor->add_theme_color_override("font_selected_color", Color::named("white")); + inline_editor->add_theme_color_override("selection_color", get_theme_color(SNAME("accent_color"), EditorStringName(Editor))); + StyleBoxEmpty *empty_style = memnew(StyleBoxEmpty); + empty_style->set_content_margin_all(0); + inline_editor->add_theme_style_override(CoreStringName(normal), empty_style); + inline_editor->add_theme_style_override("focus", memnew(StyleBoxEmpty)); + inline_editor->add_theme_style_override("read_only", memnew(StyleBoxEmpty)); + inline_editor->add_theme_constant_override("minimum_character_width", 0); + inline_editor->set_flat(true); + + inline_editor->set_text(blend_space->get_blend_point_name(p_point)); + inline_editor->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + inline_editor->set_expand_to_text_length_enabled(true); + + if (p_point < text_rects.size() && p_point < points.size()) { + Rect2 text_rect = text_rects[p_point]; + + inline_editor_point_x = points[p_point].x; + + float editor_width = text_rect.size.x; + inline_editor->set_size(Vector2(editor_width, text_rect.size.y)); + + Size2 s = blend_space_draw->get_size(); + + float editor_x = inline_editor_point_x - editor_width / 2.0; + editor_x = CLAMP(editor_x, 0.0f, s.width - editor_width); + inline_editor->set_position(Vector2(editor_x, text_rect.position.y - 1 * EDSCALE)); + } + + inline_editor->connect(SceneStringName(text_changed), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_inline_editor_text_changed)); + inline_editor->connect(SceneStringName(text_submitted), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_finish_inline_edit_with_text)); + inline_editor->connect(SceneStringName(focus_exited), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_finish_inline_edit)); + + inline_editor->grab_focus(); + inline_editor->select_all(); + + blend_space_draw->queue_redraw(); +} + +void AnimationNodeBlendSpace2DEditor::_finish_inline_edit() { + if (editing_point == -1 || !inline_editor) { + return; + } + + String new_name = inline_editor->get_text(); + _edit_point_name(new_name); + + _cancel_inline_edit(); +} + +void AnimationNodeBlendSpace2DEditor::_finish_inline_edit_with_text(const String &p_text) { + if (editing_point == -1 || !inline_editor) { + return; + } + + _edit_point_name(p_text); + + _cancel_inline_edit(); +} + +void AnimationNodeBlendSpace2DEditor::_cancel_inline_edit() { + if (inline_editor) { + inline_editor->queue_free(); + inline_editor = nullptr; + } + editing_point = -1; + blend_space_draw->queue_redraw(); +} + +void AnimationNodeBlendSpace2DEditor::_inline_editor_text_changed(const String &p_text) { + if (!inline_editor) { + return; + } + + inline_editor->set_size(Vector2(0, inline_editor->get_size().y)); + + Vector2 editor_size = inline_editor->get_size(); + Size2 s = blend_space_draw->get_size(); + + float editor_x = inline_editor_point_x - editor_size.x / 2.0; + editor_x = CLAMP(editor_x, 0.0f, s.width - editor_size.x); + + inline_editor->set_position(Vector2(editor_x, inline_editor->get_position().y)); } AnimationNodeBlendSpace2DEditor *AnimationNodeBlendSpace2DEditor::singleton = nullptr; @@ -903,7 +1190,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { tool_select->set_button_group(bg); top_hb->add_child(tool_select); tool_select->set_pressed(true); - tool_select->set_tooltip_text(TTR("Select and move points.\nRMB: Create point at position clicked.\nShift+LMB+Drag: Set the blending position within the space.")); + tool_select->set_tooltip_text(TTR("Select and move points.\nRMB: Create point at position clicked.\nShift+LMB+Drag: Set the blending position within the space.\nScroll: Increment or decrement index.")); tool_select->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_tool_switch).bind(0)); tool_create = memnew(Button); @@ -976,22 +1263,46 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { top_hb->add_child(memnew(VSeparator)); - top_hb->add_child(memnew(Label(TTR("Sync:")))); + top_hb->add_child(memnew(Label(TTR("Sync")))); sync = memnew(CheckBox); top_hb->add_child(sync); sync->connect(SceneStringName(toggled), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_config_changed)); top_hb->add_child(memnew(VSeparator)); - top_hb->add_child(memnew(Label(TTR("Blend:")))); + top_hb->add_child(memnew(Label(TTR("Blend")))); interpolation = memnew(OptionButton); top_hb->add_child(interpolation); interpolation->connect(SceneStringName(item_selected), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_config_changed)); + top_hb->add_spacer(); + edit_hb = memnew(HBoxContainer); top_hb->add_child(edit_hb); + + open_editor = memnew(Button); + edit_hb->add_child(open_editor); + open_editor->set_text(TTR("Open Editor")); + open_editor->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_open_editor), CONNECT_DEFERRED); + open_editor_sep = memnew(VSeparator); + edit_hb->add_child(open_editor_sep); + + edit_hb->add_child(memnew(Label(TTR("Index")))); + index_edit = memnew(SpinBox); + edit_hb->add_child(index_edit); + index_edit->set_min(0); + index_edit->set_step(1); + index_edit->set_allow_greater(false); + index_edit->set_allow_lesser(false); + index_edit->set_accessibility_name(TTRC("Blend Point Index")); + index_edit->set_tooltip_text(TTR("Index of the blend point.\nValues outside of the valid range will be clamped to the nearest index.")); + index_edit->connect(SceneStringName(value_changed), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_edit_point_index)); + index_edit->get_line_edit()->connect(SceneStringName(focus_entered), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_index_edit_focus_entered)); + index_edit->get_line_edit()->connect(SceneStringName(focus_exited), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_index_edit_focus_exited)); + edit_hb->add_child(memnew(VSeparator)); - edit_hb->add_child(memnew(Label(TTR("Point")))); + + edit_hb->add_child(memnew(Label(TTR("Position")))); edit_x = memnew(SpinBox); edit_hb->add_child(edit_x); edit_x->set_min(-1000); @@ -1006,12 +1317,10 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { edit_y->set_max(1000); edit_y->set_accessibility_name(TTRC("Blend Y Value")); edit_y->connect(SceneStringName(value_changed), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_edit_point_pos)); - open_editor = memnew(Button); - edit_hb->add_child(open_editor); - open_editor->set_text(TTR("Open Editor")); - open_editor->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_open_editor), CONNECT_DEFERRED); + edit_hb->hide(); open_editor->hide(); + open_editor_sep->hide(); HBoxContainer *main_hb = memnew(HBoxContainer); add_child(main_hb); @@ -1119,6 +1428,13 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { open_file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); open_file->connect("file_selected", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_file_opened)); + // Create timer for index focus cooldown (1.5 seconds). + index_focus_cooldown_timer = memnew(Timer); + add_child(index_focus_cooldown_timer); + index_focus_cooldown_timer->set_wait_time(1.5); + index_focus_cooldown_timer->set_one_shot(true); + index_focus_cooldown_timer->connect("timeout", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_index_focus_cooldown_timeout)); + selected_point = -1; selected_triangle = -1; diff --git a/editor/animation/animation_blend_space_2d_editor.h b/editor/animation/animation_blend_space_2d_editor.h index 684e7c4524bf..a2e6144f49bf 100644 --- a/editor/animation/animation_blend_space_2d_editor.h +++ b/editor/animation/animation_blend_space_2d_editor.h @@ -41,6 +41,7 @@ class LineEdit; class OptionButton; class PanelContainer; class SpinBox; +class Timer; class VSeparator; class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin { @@ -75,6 +76,8 @@ class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin { SpinBox *edit_x = nullptr; SpinBox *edit_y = nullptr; Button *open_editor = nullptr; + VSeparator *open_editor_sep = nullptr; + SpinBox *index_edit = nullptr; int selected_point; int selected_triangle; @@ -109,6 +112,14 @@ class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin { Vector2 drag_from; Vector2 drag_ofs; + Vector text_rects; + int editing_point = -1; + LineEdit *inline_editor = nullptr; + float inline_editor_point_x = 0.0f; + bool index_edit_has_focus = false; + bool show_indices = false; + Timer *index_focus_cooldown_timer = nullptr; + Vector making_triangle; void _add_menu_type(int p_index); @@ -116,14 +127,28 @@ class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin { void _tool_switch(int p_tool); void _update_edited_point_pos(); + void _update_edited_point_name(); void _update_tool_erase(); void _erase_selected(); void _edit_point_pos(double); + void _edit_point_name(const String &p_name); + void _edit_point_index(double p_index); + void _set_selected_point(int p_index); + void _start_inline_edit(int p_point); + void _finish_inline_edit(); + void _finish_inline_edit_with_text(const String &p_text); + void _cancel_inline_edit(); + void _inline_editor_text_changed(const String &p_text); void _open_editor(); + void _index_edit_focus_entered(); + void _index_edit_focus_exited(); + void _index_focus_cooldown_timeout(); + void _show_indices_with_cooldown(); void _auto_triangles_toggled(); StringName get_blend_position_path() const; + String _get_safe_name(const Ref &p_blend_space, const String &p_name); EditorFileDialog *open_file = nullptr; Ref file_loaded; @@ -143,6 +168,7 @@ class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin { public: static AnimationNodeBlendSpace2DEditor *get_singleton() { return singleton; } + void refresh_editor() { _update_space(); } virtual bool can_edit(const Ref &p_node) override; virtual void edit(const Ref &p_node) override; AnimationNodeBlendSpace2DEditor(); diff --git a/misc/extension_api_validation/4.6-stable/GH-110369.txt b/misc/extension_api_validation/4.6-stable/GH-110369.txt new file mode 100644 index 000000000000..090b0d7e0829 --- /dev/null +++ b/misc/extension_api_validation/4.6-stable/GH-110369.txt @@ -0,0 +1,6 @@ +GH-110369 +-------------- +Validate extension JSON: Error: Field 'classes/AnimationNodeBlendSpace2D/methods/add_blend_point/arguments': size changed value in new API, from 3 to 4. +Validate extension JSON: Error: Field 'classes/AnimationNodeBlendSpace1D/methods/add_blend_point/arguments': size changed value in new API, from 3 to 4. + +Added "name" parameter, to functionally replace index reference for points. Compatibility methods registered. diff --git a/scene/animation/animation_blend_space_1d.compat.inc b/scene/animation/animation_blend_space_1d.compat.inc new file mode 100644 index 000000000000..87bbf24d3d67 --- /dev/null +++ b/scene/animation/animation_blend_space_1d.compat.inc @@ -0,0 +1,49 @@ +/**************************************************************************/ +/* animation_blend_space_1d.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +#include "animation_blend_space_1d.h" + +#include "core/object/class_db.h" + +void AnimationNodeBlendSpace1D::_add_blend_point_bind_compat_110369(const Ref &p_node, float p_position, int p_at_index) { + int n = p_at_index == -1 ? blend_points_used : p_at_index; + while (find_blend_point_by_name(itos(n)) != -1) { + n++; + } + add_blend_point(p_node, p_position, p_at_index, StringName(itos(n))); +} + +void AnimationNodeBlendSpace1D::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace1D::_add_blend_point_bind_compat_110369, DEFVAL(-1)); +} + +#endif // DISABLE_DEPRECATED diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp index 691e6a1b31ea..41e95231724f 100644 --- a/scene/animation/animation_blend_space_1d.cpp +++ b/scene/animation/animation_blend_space_1d.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "animation_blend_space_1d.h" +#include "animation_blend_space_1d.compat.inc" #include "animation_blend_tree.h" @@ -54,17 +55,11 @@ Variant AnimationNodeBlendSpace1D::get_parameter_default_value(const StringName } Ref AnimationNodeBlendSpace1D::get_child_by_name(const StringName &p_name) const { - return get_blend_point_node(p_name.operator String().to_int()); -} - -void AnimationNodeBlendSpace1D::_validate_property(PropertyInfo &p_property) const { - if (p_property.name.begins_with("blend_point_")) { - String left = p_property.name.get_slicec('/', 0); - int idx = left.get_slicec('_', 2).to_int(); - if (idx >= blend_points_used) { - p_property.usage = PROPERTY_USAGE_NONE; - } + int point_index = find_blend_point_by_name(p_name); + if (point_index != -1) { + return get_blend_point_node(point_index); } + return Ref(); } void AnimationNodeBlendSpace1D::_tree_changed() { @@ -80,13 +75,17 @@ void AnimationNodeBlendSpace1D::_animation_node_removed(const ObjectID &p_oid, c } void AnimationNodeBlendSpace1D::_bind_methods() { - ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace1D::add_blend_point, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index", "name"), &AnimationNodeBlendSpace1D::add_blend_point, DEFVAL(-1), DEFVAL(StringName())); ClassDB::bind_method(D_METHOD("set_blend_point_position", "point", "pos"), &AnimationNodeBlendSpace1D::set_blend_point_position); ClassDB::bind_method(D_METHOD("get_blend_point_position", "point"), &AnimationNodeBlendSpace1D::get_blend_point_position); ClassDB::bind_method(D_METHOD("set_blend_point_node", "point", "node"), &AnimationNodeBlendSpace1D::set_blend_point_node); ClassDB::bind_method(D_METHOD("get_blend_point_node", "point"), &AnimationNodeBlendSpace1D::get_blend_point_node); + ClassDB::bind_method(D_METHOD("set_blend_point_name", "point", "name"), &AnimationNodeBlendSpace1D::set_blend_point_name); + ClassDB::bind_method(D_METHOD("get_blend_point_name", "point"), &AnimationNodeBlendSpace1D::get_blend_point_name); + ClassDB::bind_method(D_METHOD("find_blend_point_by_name", "name"), &AnimationNodeBlendSpace1D::find_blend_point_by_name); ClassDB::bind_method(D_METHOD("remove_blend_point", "point"), &AnimationNodeBlendSpace1D::remove_blend_point); ClassDB::bind_method(D_METHOD("get_blend_point_count"), &AnimationNodeBlendSpace1D::get_blend_point_count); + ClassDB::bind_method(D_METHOD("reorder_blend_point", "from_index", "to_index"), &AnimationNodeBlendSpace1D::reorder_blend_point); ClassDB::bind_method(D_METHOD("set_min_space", "min_space"), &AnimationNodeBlendSpace1D::set_min_space); ClassDB::bind_method(D_METHOD("get_min_space"), &AnimationNodeBlendSpace1D::get_min_space); @@ -106,13 +105,6 @@ void AnimationNodeBlendSpace1D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlendSpace1D::set_use_sync); ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlendSpace1D::is_using_sync); - ClassDB::bind_method(D_METHOD("_add_blend_point", "index", "node"), &AnimationNodeBlendSpace1D::_add_blend_point); - - for (int i = 0; i < MAX_BLEND_POINTS; i++) { - ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "blend_point_" + itos(i) + "/node", PROPERTY_HINT_RESOURCE_TYPE, AnimationRootNode::get_class_static(), PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_add_blend_point", "get_blend_point_node", i); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "blend_point_" + itos(i) + "/pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_blend_point_position", "get_blend_point_position", i); - } - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_min_space", "get_min_space"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_max_space", "get_max_space"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_snap", "get_snap"); @@ -128,18 +120,25 @@ void AnimationNodeBlendSpace1D::_bind_methods() { void AnimationNodeBlendSpace1D::get_child_nodes(List *r_child_nodes) { for (int i = 0; i < blend_points_used; i++) { ChildNode cn; - cn.name = itos(i); + cn.name = blend_points[i].name; cn.node = blend_points[i].node; r_child_nodes->push_back(cn); } } -void AnimationNodeBlendSpace1D::add_blend_point(const Ref &p_node, float p_position, int p_at_index) { +void AnimationNodeBlendSpace1D::add_blend_point(const Ref &p_node, float p_position, int p_at_index, const StringName &p_name) { ERR_FAIL_COND(blend_points_used >= MAX_BLEND_POINTS); ERR_FAIL_COND(p_node.is_null()); - ERR_FAIL_COND(p_at_index < -1 || p_at_index > blend_points_used); - +#ifndef DISABLE_DEPRECATED + if (p_name == StringName()) { + _add_blend_point_bind_compat_110369(p_node, p_position, p_at_index); + WARN_PRINT_ED("AnimationNodeBlendSpace1D::add_blend_point: No name provided, using safe index as reference. In the future, empty names will be deprecated, so explicitly passing a name is recommended."); + return; + } +#else + ERR_FAIL_COND(p_name == StringName()); +#endif if (p_at_index == -1 || p_at_index == blend_points_used) { p_at_index = blend_points_used; } else { @@ -150,6 +149,7 @@ void AnimationNodeBlendSpace1D::add_blend_point(const Ref &p_ blend_points[p_at_index].node = p_node; blend_points[p_at_index].position = p_position; + blend_points[p_at_index].name = p_name; blend_points[p_at_index].node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace1D::_tree_changed), CONNECT_REFERENCE_COUNTED); blend_points[p_at_index].node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_renamed), CONNECT_REFERENCE_COUNTED); @@ -193,6 +193,32 @@ Ref AnimationNodeBlendSpace1D::get_blend_point_node(int p_poi return blend_points[p_point].node; } +void AnimationNodeBlendSpace1D::set_blend_point_name(int p_point, const StringName &p_name) { + ERR_FAIL_INDEX(p_point, blend_points_used); + String new_name = p_name; + ERR_FAIL_COND(new_name.is_empty() || new_name.contains_char('.') || new_name.contains_char('/')); + + String old_name = blend_points[p_point].name; + if (new_name != old_name) { + blend_points[p_point].name = p_name; + emit_signal(SNAME("animation_node_renamed"), get_instance_id(), old_name, new_name); + } +} + +StringName AnimationNodeBlendSpace1D::get_blend_point_name(int p_point) const { + ERR_FAIL_INDEX_V(p_point, blend_points_used, StringName()); + return blend_points[p_point].name; +} + +int AnimationNodeBlendSpace1D::find_blend_point_by_name(const StringName &p_name) const { + for (int i = 0; i < blend_points_used; i++) { + if (blend_points[i].name == p_name) { + return i; + } + } + return -1; +} + void AnimationNodeBlendSpace1D::remove_blend_point(int p_point) { ERR_FAIL_INDEX(p_point, blend_points_used); @@ -207,6 +233,8 @@ void AnimationNodeBlendSpace1D::remove_blend_point(int p_point) { blend_points_used--; + blend_points[blend_points_used].name = StringName(); + emit_signal(SNAME("animation_node_removed"), get_instance_id(), itos(p_point)); emit_signal(SNAME("tree_changed")); } @@ -215,6 +243,22 @@ int AnimationNodeBlendSpace1D::get_blend_point_count() const { return blend_points_used; } +void AnimationNodeBlendSpace1D::reorder_blend_point(int p_from_index, int p_to_index) { + ERR_FAIL_INDEX(p_from_index, blend_points_used); + ERR_FAIL_INDEX(p_to_index, blend_points_used); + + if (p_from_index == p_to_index) { + return; + } + + BlendPoint temp = blend_points[p_from_index]; + + blend_points[p_from_index] = blend_points[p_to_index]; + blend_points[p_to_index] = temp; + + emit_signal(SNAME("tree_changed")); +} + void AnimationNodeBlendSpace1D::set_min_space(float p_min) { min_space = p_min; @@ -271,11 +315,62 @@ bool AnimationNodeBlendSpace1D::is_using_sync() const { return sync; } -void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Ref &p_node) { - if (p_index == blend_points_used) { - add_blend_point(p_node, 0); - } else { - set_blend_point_node(p_index, p_node); +bool AnimationNodeBlendSpace1D::_set(const StringName &p_name, const Variant &p_value) { + String prop = p_name; + if (prop.begins_with("blend_point_")) { + int idx = prop.get_slicec('_', 2).to_int(); + String what = prop.get_slicec('/', 1); + if (what == "node") { + Ref node = p_value; +#ifndef DISABLE_DEPRECATED + if (idx == blend_points_used) { + add_blend_point(node, 0, -1, blend_points[idx].name.is_empty() ? StringName(itos(idx)) : blend_points[idx].name); + } else { + set_blend_point_node(idx, node); + } +#else + if (idx == blend_points_used) { + add_blend_point(node, 0, -1, blend_points[idx].name); + } else { + set_blend_point_node(idx, node); + } +#endif // DISABLE_DEPRECATED + } else if (what == "pos") { + set_blend_point_position(idx, p_value); + } else if (what == "name") { + set_blend_point_name(idx, p_value); + } else { + return false; + } + return true; + } + return false; +} + +bool AnimationNodeBlendSpace1D::_get(const StringName &p_name, Variant &r_ret) const { + String prop = p_name; + if (prop.begins_with("blend_point_")) { + int idx = prop.get_slicec('_', 2).to_int(); + String what = prop.get_slicec('/', 1); + if (what == "node") { + r_ret = get_blend_point_node(idx); + } else if (what == "pos") { + r_ret = get_blend_point_position(idx); + } else if (what == "name") { + r_ret = get_blend_point_name(idx); + } else { + return false; + } + return true; + } + return false; +} + +void AnimationNodeBlendSpace1D::_get_property_list(List *p_list) const { + for (int i = 0; i < blend_points_used; i++) { + p_list->push_back(PropertyInfo(Variant::OBJECT, "blend_point_" + itos(i) + "/node", PROPERTY_HINT_RESOURCE_TYPE, AnimationRootNode::get_class_static(), PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::FLOAT, "blend_point_" + itos(i) + "/pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::STRING, "blend_point_" + itos(i) + "/name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); } } @@ -289,7 +384,7 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace1D::_process(const AnimationM if (blend_points_used == 1) { // only one point available, just play that animation pi.weight = 1.0; - return blend_node(blend_points[0].node, blend_points[0].name, pi, FILTER_IGNORE, true, p_test_only); + return blend_node(blend_points[0].node, get_blend_point_name(0), pi, FILTER_IGNORE, true, p_test_only); } double blend_pos = get_parameter(blend_position); @@ -352,7 +447,7 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace1D::_process(const AnimationM for (int i = 0; i < blend_points_used; i++) { if (i == point_lower || i == point_higher) { pi.weight = weights[i]; - NodeTimeInfo t = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only); + NodeTimeInfo t = blend_node(blend_points[i].node, get_blend_point_name(i), pi, FILTER_IGNORE, true, p_test_only); if (first || pi.weight > max_weight) { max_weight = pi.weight; mind = t; @@ -360,7 +455,7 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace1D::_process(const AnimationM } } else if (sync) { pi.weight = 0; - blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only); + blend_node(blend_points[i].node, get_blend_point_name(i), pi, FILTER_IGNORE, true, p_test_only); } } } else { @@ -393,16 +488,16 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace1D::_process(const AnimationM // See how much animation remains. pi.seeked = false; pi.weight = 0; - from = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, true); + from = blend_node(blend_points[cur_closest].node, get_blend_point_name(cur_closest), pi, FILTER_IGNORE, true, true); pi.time = from.position; } pi.seeked = true; pi.weight = 1.0; - mind = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only); + mind = blend_node(blend_points[new_closest].node, get_blend_point_name(new_closest), pi, FILTER_IGNORE, true, p_test_only); cur_closest = new_closest; } else { pi.weight = 1.0; - mind = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only); + mind = blend_node(blend_points[cur_closest].node, get_blend_point_name(cur_closest), pi, FILTER_IGNORE, true, p_test_only); } if (sync) { @@ -410,7 +505,7 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace1D::_process(const AnimationM pi.weight = 0; for (int i = 0; i < blend_points_used; i++) { if (i != cur_closest) { - blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only); + blend_node(blend_points[i].node, get_blend_point_name(i), pi, FILTER_IGNORE, true, p_test_only); } } } @@ -425,9 +520,6 @@ String AnimationNodeBlendSpace1D::get_caption() const { } AnimationNodeBlendSpace1D::AnimationNodeBlendSpace1D() { - for (int i = 0; i < MAX_BLEND_POINTS; i++) { - blend_points[i].name = itos(i); - } } AnimationNodeBlendSpace1D::~AnimationNodeBlendSpace1D() { diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h index 0bd29cd97e9f..fc1bb0af37b1 100644 --- a/scene/animation/animation_blend_space_1d.h +++ b/scene/animation/animation_blend_space_1d.h @@ -63,7 +63,9 @@ class AnimationNodeBlendSpace1D : public AnimationRootNode { String value_label = "value"; - void _add_blend_point(int p_index, const Ref &p_node); + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; StringName blend_position = "blend_position"; StringName closest = "closest"; @@ -72,28 +74,37 @@ class AnimationNodeBlendSpace1D : public AnimationRootNode { bool sync = false; - void _validate_property(PropertyInfo &p_property) const; static void _bind_methods(); virtual void _tree_changed() override; virtual void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) override; virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node) override; +#ifndef DISABLE_DEPRECATED + void _add_blend_point_bind_compat_110369(const Ref &p_node, float p_position, int p_at_index = -1); + static void _bind_compatibility_methods(); +#endif + public: virtual void get_parameter_list(List *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; virtual void get_child_nodes(List *r_child_nodes) override; - void add_blend_point(const Ref &p_node, float p_position, int p_at_index = -1); + void add_blend_point(const Ref &p_node, float p_position, int p_at_index = -1, const StringName &p_name = ""); void set_blend_point_position(int p_point, float p_position); void set_blend_point_node(int p_point, const Ref &p_node); float get_blend_point_position(int p_point) const; Ref get_blend_point_node(int p_point) const; + void set_blend_point_name(int p_point, const StringName &p_name); + StringName get_blend_point_name(int p_point) const; + int find_blend_point_by_name(const StringName &p_name) const; void remove_blend_point(int p_point); int get_blend_point_count() const; + void reorder_blend_point(int p_from_index, int p_to_index); + void set_min_space(float p_min); float get_min_space() const; diff --git a/scene/animation/animation_blend_space_2d.compat.inc b/scene/animation/animation_blend_space_2d.compat.inc new file mode 100644 index 000000000000..59994aa04254 --- /dev/null +++ b/scene/animation/animation_blend_space_2d.compat.inc @@ -0,0 +1,49 @@ +/**************************************************************************/ +/* animation_blend_space_2d.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +#include "animation_blend_space_2d.h" + +#include "core/object/class_db.h" + +void AnimationNodeBlendSpace2D::_add_blend_point_bind_compat_110369(const Ref &p_node, const Vector2 &p_position, int p_at_index) { + int n = p_at_index == -1 ? blend_points_used : p_at_index; + while (find_blend_point_by_name(itos(n)) != -1) { + n++; + } + add_blend_point(p_node, p_position, p_at_index, StringName(itos(n))); +} + +void AnimationNodeBlendSpace2D::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace2D::_add_blend_point_bind_compat_110369, DEFVAL(-1)); +} + +#endif // DISABLE_DEPRECATED diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp index d3a23f8fcc5b..bacae427f5c6 100644 --- a/scene/animation/animation_blend_space_2d.cpp +++ b/scene/animation/animation_blend_space_2d.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "animation_blend_space_2d.h" +#include "animation_blend_space_2d.compat.inc" #include "animation_blend_tree.h" @@ -58,16 +59,25 @@ Variant AnimationNodeBlendSpace2D::get_parameter_default_value(const StringName void AnimationNodeBlendSpace2D::get_child_nodes(List *r_child_nodes) { for (int i = 0; i < blend_points_used; i++) { ChildNode cn; - cn.name = itos(i); + cn.name = blend_points[i].name; cn.node = blend_points[i].node; r_child_nodes->push_back(cn); } } -void AnimationNodeBlendSpace2D::add_blend_point(const Ref &p_node, const Vector2 &p_position, int p_at_index) { +void AnimationNodeBlendSpace2D::add_blend_point(const Ref &p_node, const Vector2 &p_position, int p_at_index, const StringName &p_name) { ERR_FAIL_COND(blend_points_used >= MAX_BLEND_POINTS); ERR_FAIL_COND(p_node.is_null()); ERR_FAIL_COND(p_at_index < -1 || p_at_index > blend_points_used); +#ifndef DISABLE_DEPRECATED + if (p_name == StringName()) { + _add_blend_point_bind_compat_110369(p_node, p_position, p_at_index); + WARN_PRINT_ED("AnimationNodeBlendSpace2D::add_blend_point: No name provided, using safe index as reference. In the future, empty names will be deprecated, so explicitly passing a name is recommended."); + return; + } +#else + ERR_FAIL_COND(p_name == StringName()); +#endif if (p_at_index == -1 || p_at_index == blend_points_used) { p_at_index = blend_points_used; @@ -85,6 +95,7 @@ void AnimationNodeBlendSpace2D::add_blend_point(const Ref &p_ } blend_points[p_at_index].node = p_node; blend_points[p_at_index].position = p_position; + blend_points[p_at_index].name = p_name; blend_points[p_at_index].node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace2D::_tree_changed), CONNECT_REFERENCE_COUNTED); blend_points[p_at_index].node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_renamed), CONNECT_REFERENCE_COUNTED); @@ -129,6 +140,32 @@ Ref AnimationNodeBlendSpace2D::get_blend_point_node(int p_poi return blend_points[p_point].node; } +void AnimationNodeBlendSpace2D::set_blend_point_name(int p_point, const StringName &p_name) { + ERR_FAIL_INDEX(p_point, blend_points_used); + String new_name = p_name; + ERR_FAIL_COND(new_name.is_empty() || new_name.contains_char('.') || new_name.contains_char('/')); + + String old_name = blend_points[p_point].name; + if (new_name != old_name) { + blend_points[p_point].name = p_name; + emit_signal(SNAME("animation_node_renamed"), get_instance_id(), old_name, p_name); + } +} + +StringName AnimationNodeBlendSpace2D::get_blend_point_name(int p_point) const { + ERR_FAIL_INDEX_V(p_point, blend_points_used, StringName()); + return blend_points[p_point].name; +} + +int AnimationNodeBlendSpace2D::find_blend_point_by_name(const StringName &p_name) const { + for (int i = 0; i < blend_points_used; i++) { + if (blend_points[i].name == p_name) { + return i; + } + } + return -1; +} + void AnimationNodeBlendSpace2D::remove_blend_point(int p_point) { ERR_FAIL_INDEX(p_point, blend_points_used); @@ -159,6 +196,8 @@ void AnimationNodeBlendSpace2D::remove_blend_point(int p_point) { } blend_points_used--; + blend_points[blend_points_used].name = StringName(); + emit_signal(SNAME("animation_node_removed"), get_instance_id(), itos(p_point)); emit_signal(SNAME("tree_changed")); } @@ -167,6 +206,38 @@ int AnimationNodeBlendSpace2D::get_blend_point_count() const { return blend_points_used; } +void AnimationNodeBlendSpace2D::reorder_blend_point(int p_from_index, int p_to_index) { + ERR_FAIL_INDEX(p_from_index, blend_points_used); + ERR_FAIL_INDEX(p_to_index, blend_points_used); + + if (p_from_index == p_to_index) { + return; + } + + // Update triangle indices for swap operation + for (int i = 0; i < triangles.size(); i++) { + for (int j = 0; j < 3; j++) { + int point_ref = triangles[i].points[j]; + + if (point_ref == p_from_index) { + triangles.write[i].points[j] = p_to_index; + } else if (point_ref == p_to_index) { + triangles.write[i].points[j] = p_from_index; + } + } + } + + BlendPoint temp = blend_points[p_from_index]; + + blend_points[p_from_index] = blend_points[p_to_index]; + blend_points[p_to_index] = temp; + + _queue_auto_triangles(); + + emit_signal(SNAME("tree_changed")); + emit_signal(SNAME("triangles_updated")); +} + bool AnimationNodeBlendSpace2D::has_triangle(int p_x, int p_y, int p_z) const { ERR_FAIL_INDEX_V(p_x, blend_points_used, false); ERR_FAIL_INDEX_V(p_y, blend_points_used, false); @@ -299,14 +370,6 @@ String AnimationNodeBlendSpace2D::get_y_label() const { return y_label; } -void AnimationNodeBlendSpace2D::_add_blend_point(int p_index, const Ref &p_node) { - if (p_index == blend_points_used) { - add_blend_point(p_node, Vector2()); - } else { - set_blend_point_node(p_index, p_node); - } -} - void AnimationNodeBlendSpace2D::_set_triangles(const Vector &p_triangles) { if (auto_triangles) { return; @@ -332,6 +395,65 @@ Vector AnimationNodeBlendSpace2D::_get_triangles() const { return t; } +bool AnimationNodeBlendSpace2D::_set(const StringName &p_name, const Variant &p_value) { + String prop = p_name; + if (prop.begins_with("blend_point_")) { + int idx = prop.get_slicec('_', 2).to_int(); + String what = prop.get_slicec('/', 1); + if (what == "node") { + Ref node = p_value; +#ifndef DISABLE_DEPRECATED + if (idx == blend_points_used) { + add_blend_point(node, Vector2(), -1, blend_points[idx].name.is_empty() ? StringName(itos(idx)) : blend_points[idx].name); + } else { + set_blend_point_node(idx, node); + } +#else + if (idx == blend_points_used) { + add_blend_point(node, Vector2(), -1, blend_points[idx].name); + } else { + set_blend_point_node(idx, node); + } +#endif // DISABLE_DEPRECATED + } else if (what == "pos") { + set_blend_point_position(idx, p_value); + } else if (what == "name") { + set_blend_point_name(idx, p_value); + } else { + return false; + } + return true; + } + return false; +} + +bool AnimationNodeBlendSpace2D::_get(const StringName &p_name, Variant &r_ret) const { + String prop = p_name; + if (prop.begins_with("blend_point_")) { + int idx = prop.get_slicec('_', 2).to_int(); + String what = prop.get_slicec('/', 1); + if (what == "node") { + r_ret = get_blend_point_node(idx); + } else if (what == "pos") { + r_ret = get_blend_point_position(idx); + } else if (what == "name") { + r_ret = get_blend_point_name(idx); + } else { + return false; + } + return true; + } + return false; +} + +void AnimationNodeBlendSpace2D::_get_property_list(List *p_list) const { + for (int i = 0; i < blend_points_used; i++) { + p_list->push_back(PropertyInfo(Variant::OBJECT, "blend_point_" + itos(i) + "/node", PROPERTY_HINT_RESOURCE_TYPE, AnimationRootNode::get_class_static(), PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "blend_point_" + itos(i) + "/pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::STRING, "blend_point_" + itos(i) + "/name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); + } +} + void AnimationNodeBlendSpace2D::_queue_auto_triangles() { if (!auto_triangles || triangles_dirty) { return; @@ -521,7 +643,7 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace2D::_process(const AnimationM if (i == triangle_points[j]) { //blend with the given weight pi.weight = blend_weights[j]; - NodeTimeInfo t = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only); + NodeTimeInfo t = blend_node(blend_points[i].node, get_blend_point_name(i), pi, FILTER_IGNORE, true, p_test_only); if (first || pi.weight > max_weight) { mind = t; max_weight = pi.weight; @@ -534,7 +656,7 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace2D::_process(const AnimationM if (sync && !found) { pi.weight = 0; - blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only); + blend_node(blend_points[i].node, get_blend_point_name(i), pi, FILTER_IGNORE, true, p_test_only); } } } else { @@ -567,16 +689,16 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace2D::_process(const AnimationM // See how much animation remains. pi.seeked = false; pi.weight = 0; - from = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, true); + from = blend_node(blend_points[cur_closest].node, get_blend_point_name(cur_closest), pi, FILTER_IGNORE, true, true); pi.time = from.position; } pi.seeked = true; pi.weight = 1.0; - mind = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only); + mind = blend_node(blend_points[new_closest].node, get_blend_point_name(new_closest), pi, FILTER_IGNORE, true, p_test_only); cur_closest = new_closest; } else { pi.weight = 1.0; - mind = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only); + mind = blend_node(blend_points[cur_closest].node, get_blend_point_name(cur_closest), pi, FILTER_IGNORE, true, p_test_only); } if (sync) { @@ -584,7 +706,7 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace2D::_process(const AnimationM pi.weight = 0; for (int i = 0; i < blend_points_used; i++) { if (i != cur_closest) { - blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only); + blend_node(blend_points[i].node, get_blend_point_name(i), pi, FILTER_IGNORE, true, p_test_only); } } } @@ -601,12 +723,6 @@ String AnimationNodeBlendSpace2D::get_caption() const { void AnimationNodeBlendSpace2D::_validate_property(PropertyInfo &p_property) const { if (auto_triangles && p_property.name == "triangles") { p_property.usage = PROPERTY_USAGE_NONE; - } else if (p_property.name.begins_with("blend_point_")) { - String left = p_property.name.get_slicec('/', 0); - int idx = left.get_slicec('_', 2).to_int(); - if (idx >= blend_points_used) { - p_property.usage = PROPERTY_USAGE_NONE; - } } } @@ -624,7 +740,11 @@ bool AnimationNodeBlendSpace2D::get_auto_triangles() const { } Ref AnimationNodeBlendSpace2D::get_child_by_name(const StringName &p_name) const { - return get_blend_point_node(p_name.operator String().to_int()); + int point_index = find_blend_point_by_name(p_name); + if (point_index != -1) { + return get_blend_point_node(point_index); + } + return Ref(); } void AnimationNodeBlendSpace2D::set_blend_mode(BlendMode p_blend_mode) { @@ -656,13 +776,17 @@ void AnimationNodeBlendSpace2D::_animation_node_removed(const ObjectID &p_oid, c } void AnimationNodeBlendSpace2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace2D::add_blend_point, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index", "name"), &AnimationNodeBlendSpace2D::add_blend_point, DEFVAL(-1), DEFVAL(StringName())); ClassDB::bind_method(D_METHOD("set_blend_point_position", "point", "pos"), &AnimationNodeBlendSpace2D::set_blend_point_position); ClassDB::bind_method(D_METHOD("get_blend_point_position", "point"), &AnimationNodeBlendSpace2D::get_blend_point_position); ClassDB::bind_method(D_METHOD("set_blend_point_node", "point", "node"), &AnimationNodeBlendSpace2D::set_blend_point_node); ClassDB::bind_method(D_METHOD("get_blend_point_node", "point"), &AnimationNodeBlendSpace2D::get_blend_point_node); + ClassDB::bind_method(D_METHOD("set_blend_point_name", "point", "name"), &AnimationNodeBlendSpace2D::set_blend_point_name); + ClassDB::bind_method(D_METHOD("get_blend_point_name", "point"), &AnimationNodeBlendSpace2D::get_blend_point_name); + ClassDB::bind_method(D_METHOD("find_blend_point_by_name", "name"), &AnimationNodeBlendSpace2D::find_blend_point_by_name); ClassDB::bind_method(D_METHOD("remove_blend_point", "point"), &AnimationNodeBlendSpace2D::remove_blend_point); ClassDB::bind_method(D_METHOD("get_blend_point_count"), &AnimationNodeBlendSpace2D::get_blend_point_count); + ClassDB::bind_method(D_METHOD("reorder_blend_point", "from_index", "to_index"), &AnimationNodeBlendSpace2D::reorder_blend_point); ClassDB::bind_method(D_METHOD("add_triangle", "x", "y", "z", "at_index"), &AnimationNodeBlendSpace2D::add_triangle, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("get_triangle_point", "triangle", "point"), &AnimationNodeBlendSpace2D::get_triangle_point); @@ -684,8 +808,6 @@ void AnimationNodeBlendSpace2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_y_label", "text"), &AnimationNodeBlendSpace2D::set_y_label); ClassDB::bind_method(D_METHOD("get_y_label"), &AnimationNodeBlendSpace2D::get_y_label); - ClassDB::bind_method(D_METHOD("_add_blend_point", "index", "node"), &AnimationNodeBlendSpace2D::_add_blend_point); - ClassDB::bind_method(D_METHOD("_set_triangles", "triangles"), &AnimationNodeBlendSpace2D::_set_triangles); ClassDB::bind_method(D_METHOD("_get_triangles"), &AnimationNodeBlendSpace2D::_get_triangles); @@ -699,12 +821,6 @@ void AnimationNodeBlendSpace2D::_bind_methods() { ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlendSpace2D::is_using_sync); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_auto_triangles", "get_auto_triangles"); - - for (int i = 0; i < MAX_BLEND_POINTS; i++) { - ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "blend_point_" + itos(i) + "/node", PROPERTY_HINT_RESOURCE_TYPE, AnimationRootNode::get_class_static(), PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_add_blend_point", "get_blend_point_node", i); - ADD_PROPERTYI(PropertyInfo(Variant::VECTOR2, "blend_point_" + itos(i) + "/pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_blend_point_position", "get_blend_point_position", i); - } - ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_triangles", "_get_triangles"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_min_space", "get_min_space"); @@ -722,9 +838,6 @@ void AnimationNodeBlendSpace2D::_bind_methods() { } AnimationNodeBlendSpace2D::AnimationNodeBlendSpace2D() { - for (int i = 0; i < MAX_BLEND_POINTS; i++) { - blend_points[i].name = itos(i); - } } AnimationNodeBlendSpace2D::~AnimationNodeBlendSpace2D() { diff --git a/scene/animation/animation_blend_space_2d.h b/scene/animation/animation_blend_space_2d.h index 4c4791c26015..e0619884d508 100644 --- a/scene/animation/animation_blend_space_2d.h +++ b/scene/animation/animation_blend_space_2d.h @@ -71,7 +71,10 @@ class AnimationNodeBlendSpace2D : public AnimationRootNode { String y_label = "y"; BlendMode blend_mode = BLEND_MODE_INTERPOLATED; - void _add_blend_point(int p_index, const Ref &p_node); + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + void _set_triangles(const Vector &p_triangles); Vector _get_triangles() const; @@ -92,20 +95,30 @@ class AnimationNodeBlendSpace2D : public AnimationRootNode { virtual void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) override; virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node) override; +#ifndef DISABLE_DEPRECATED + void _add_blend_point_bind_compat_110369(const Ref &p_node, const Vector2 &p_position, int p_at_index = -1); + static void _bind_compatibility_methods(); +#endif + public: virtual void get_parameter_list(List *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; virtual void get_child_nodes(List *r_child_nodes) override; - void add_blend_point(const Ref &p_node, const Vector2 &p_position, int p_at_index = -1); + void add_blend_point(const Ref &p_node, const Vector2 &p_position, int p_at_index = -1, const StringName &p_name = StringName()); void set_blend_point_position(int p_point, const Vector2 &p_position); void set_blend_point_node(int p_point, const Ref &p_node); + void set_blend_point_name(int p_point, const StringName &p_name); + StringName get_blend_point_name(int p_point) const; + int find_blend_point_by_name(const StringName &p_name) const; Vector2 get_blend_point_position(int p_point) const; Ref get_blend_point_node(int p_point) const; void remove_blend_point(int p_point); int get_blend_point_count() const; + void reorder_blend_point(int p_from_index, int p_to_index); + bool has_triangle(int p_x, int p_y, int p_z) const; void add_triangle(int p_x, int p_y, int p_z, int p_at_index = -1); int get_triangle_point(int p_triangle, int p_point);