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);