summaryrefslogtreecommitdiff
path: root/editor/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'editor/plugins')
-rw-r--r--editor/plugins/animation_blend_space_1d_editor.cpp741
-rw-r--r--editor/plugins/animation_blend_space_1d_editor.h117
-rw-r--r--editor/plugins/animation_blend_space_2d_editor.cpp1023
-rw-r--r--editor/plugins/animation_blend_space_2d_editor.h130
-rw-r--r--editor/plugins/animation_blend_tree_editor_plugin.cpp848
-rw-r--r--editor/plugins/animation_blend_tree_editor_plugin.h117
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp255
-rw-r--r--editor/plugins/animation_player_editor_plugin.h32
-rw-r--r--editor/plugins/animation_state_machine_editor.cpp1313
-rw-r--r--editor/plugins/animation_state_machine_editor.h167
-rw-r--r--editor/plugins/asset_library_editor_plugin.cpp1
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp120
-rw-r--r--editor/plugins/canvas_item_editor_plugin.h10
-rw-r--r--editor/plugins/editor_preview_plugins.cpp387
-rw-r--r--editor/plugins/editor_preview_plugins.h35
-rw-r--r--editor/plugins/navigation_mesh_editor_plugin.cpp166
-rw-r--r--editor/plugins/navigation_mesh_editor_plugin.h87
-rw-r--r--editor/plugins/navigation_mesh_generator.cpp308
-rw-r--r--editor/plugins/navigation_mesh_generator.h66
-rw-r--r--editor/plugins/root_motion_editor_plugin.cpp293
-rw-r--r--editor/plugins/root_motion_editor_plugin.h42
-rw-r--r--editor/plugins/script_editor_plugin.cpp128
-rw-r--r--editor/plugins/script_editor_plugin.h19
-rw-r--r--editor/plugins/script_text_editor.cpp30
-rw-r--r--editor/plugins/spatial_editor_plugin.cpp72
-rw-r--r--editor/plugins/spatial_editor_plugin.h2
-rw-r--r--editor/plugins/tile_map_editor_plugin.cpp220
-rw-r--r--editor/plugins/tile_map_editor_plugin.h13
-rw-r--r--editor/plugins/tile_set_editor_plugin.cpp4
29 files changed, 5546 insertions, 1200 deletions
diff --git a/editor/plugins/animation_blend_space_1d_editor.cpp b/editor/plugins/animation_blend_space_1d_editor.cpp
new file mode 100644
index 0000000000..2e128db883
--- /dev/null
+++ b/editor/plugins/animation_blend_space_1d_editor.cpp
@@ -0,0 +1,741 @@
+#include "animation_blend_space_1d_editor.h"
+
+#include "os/keyboard.h"
+#include "scene/animation/animation_blend_tree.h"
+
+void AnimationNodeBlendSpace1DEditorPlugin::edit(Object *p_object) {
+ anim_tree_editor->edit(Object::cast_to<AnimationNodeBlendSpace1D>(p_object));
+}
+
+bool AnimationNodeBlendSpace1DEditorPlugin::handles(Object *p_object) const {
+ return p_object->is_class("AnimationNodeBlendSpace1D");
+}
+
+void AnimationNodeBlendSpace1DEditorPlugin::make_visible(bool p_visible) {
+
+ if (p_visible) {
+ button->show();
+ editor->make_bottom_panel_item_visible(anim_tree_editor);
+ anim_tree_editor->set_process(true);
+ } else {
+ if (anim_tree_editor->is_visible_in_tree()) {
+ editor->hide_bottom_panel();
+ }
+
+ button->hide();
+ anim_tree_editor->set_process(false);
+ }
+}
+
+AnimationNodeBlendSpace1DEditorPlugin::AnimationNodeBlendSpace1DEditorPlugin(EditorNode *p_node) {
+ editor = p_node;
+ anim_tree_editor = memnew(AnimationNodeBlendSpace1DEditor);
+ anim_tree_editor->set_custom_minimum_size(Size2(0, 150 * EDSCALE));
+
+ button = editor->add_bottom_panel_item(TTR("BlendSpace1D"), anim_tree_editor);
+ button->hide();
+}
+
+AnimationNodeBlendSpace1DEditorPlugin::~AnimationNodeBlendSpace1DEditorPlugin() {
+}
+
+void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEvent> &p_event) {
+ Ref<InputEventKey> k = p_event;
+
+ if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_scancode() == KEY_DELETE && !k->is_echo()) {
+ if (selected_point != -1) {
+ _erase_selected();
+ accept_event();
+ }
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+
+ if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) || (mb->get_button_index() == BUTTON_LEFT && tool_create->is_pressed()))) {
+ menu->clear();
+ animations_menu->clear();
+ animations_to_add.clear();
+
+ List<StringName> classes;
+ ClassDB::get_inheriters_from_class("AnimationRootNode", &classes);
+ classes.sort_custom<StringName::AlphCompare>();
+
+ menu->add_submenu_item(TTR("Add Animation"), "animations");
+
+ AnimationTree *gp = blend_space->get_tree();
+ ERR_FAIL_COND(!gp);
+
+ if (gp->has_node(gp->get_animation_player())) {
+ AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player()));
+
+ if (ap) {
+ List<StringName> names;
+ ap->get_animation_list(&names);
+
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ animations_menu->add_icon_item(get_icon("Animation", "Editoricons"), E->get());
+ animations_to_add.push_back(E->get());
+ }
+ }
+ }
+
+ for (List<StringName>::Element *E = classes.front(); E; E = E->next()) {
+ String name = String(E->get()).replace_first("AnimationNode", "");
+ if (name == "Animation")
+ continue;
+
+ int idx = menu->get_item_count();
+ menu->add_item(vformat("Add %s", name));
+ menu->set_item_metadata(idx, E->get());
+ }
+
+ menu->set_global_position(blend_space_draw->get_global_transform().xform(mb->get_position()));
+ menu->popup();
+
+ add_point_pos = (mb->get_position() / blend_space_draw->get_size()).x;
+ add_point_pos *= (blend_space->get_max_space() - blend_space->get_min_space());
+ add_point_pos += blend_space->get_min_space();
+
+ if (snap->is_pressed()) {
+ add_point_pos = Math::stepify(add_point_pos, blend_space->get_snap());
+ }
+ }
+
+ if (mb.is_valid() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
+ blend_space_draw->update(); // why not
+
+ // try to see if a point can be selected
+ selected_point = -1;
+ _update_tool_erase();
+
+ for (int i = 0; i < points.size(); i++) {
+
+ if (Math::abs(float(points[i] - mb->get_position().x)) < 10 * EDSCALE) {
+ selected_point = i;
+
+ Ref<AnimationNode> node = blend_space->get_blend_point_node(i);
+ EditorNode::get_singleton()->push_item(node.ptr(), "", true);
+ dragging_selected_attempt = true;
+ drag_from = mb->get_position();
+ _update_tool_erase();
+ _update_edited_point_pos();
+ return;
+ }
+ }
+ }
+
+ if (mb.is_valid() && !mb->is_pressed() && dragging_selected_attempt && mb->get_button_index() == BUTTON_LEFT) {
+ if (dragging_selected) {
+ // move
+ float point = blend_space->get_blend_point_position(selected_point);
+ point += drag_ofs.x;
+
+ if (snap->is_pressed()) {
+ point = Math::stepify(point, blend_space->get_snap());
+ }
+
+ updating = true;
+ undo_redo->create_action("Move 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");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->add_do_method(this, "_update_edited_point_pos");
+ undo_redo->add_undo_method(this, "_update_edited_point_pos");
+ undo_redo->commit_action();
+ updating = false;
+ _update_edited_point_pos();
+ }
+
+ dragging_selected_attempt = false;
+ dragging_selected = false;
+ blend_space_draw->update();
+ }
+
+ // *set* the blend
+ if (mb.is_valid() && !mb->is_pressed() && tool_blend->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
+ float blend_pos = mb->get_position().x / blend_space_draw->get_size().x;
+ blend_pos *= blend_space->get_max_space() - blend_space->get_min_space();
+ blend_pos += blend_space->get_min_space();
+
+ blend_space->set_blend_pos(blend_pos);
+ blend_space_draw->update();
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+
+ if (mm.is_valid() && !blend_space_draw->has_focus()) {
+ blend_space_draw->grab_focus();
+ blend_space_draw->update();
+ }
+
+ if (mm.is_valid() && dragging_selected_attempt) {
+ dragging_selected = true;
+ drag_ofs = ((mm->get_position() - drag_from) / blend_space_draw->get_size()) * ((blend_space->get_max_space() - blend_space->get_min_space()) * Vector2(1, 0));
+ blend_space_draw->update();
+ _update_edited_point_pos();
+ }
+
+ if (mm.is_valid() && tool_blend->is_pressed() && mm->get_button_mask() & BUTTON_MASK_LEFT) {
+ float blend_pos = mm->get_position().x / blend_space_draw->get_size().x;
+ blend_pos *= blend_space->get_max_space() - blend_space->get_min_space();
+ blend_pos += blend_space->get_min_space();
+
+ blend_space->set_blend_pos(blend_pos);
+ blend_space_draw->update();
+ }
+}
+
+void AnimationNodeBlendSpace1DEditor::_blend_space_draw() {
+
+ Color linecolor = get_color("font_color", "Label");
+ Color linecolor_soft = linecolor;
+ linecolor_soft.a *= 0.5;
+
+ Ref<Font> font = get_font("font", "Label");
+ Ref<Texture> icon = get_icon("KeyValue", "EditorIcons");
+ Ref<Texture> icon_selected = get_icon("KeySelected", "EditorIcons");
+
+ Size2 s = blend_space_draw->get_size();
+
+ if (blend_space_draw->has_focus()) {
+ Color color = get_color("accent_color", "Editor");
+ blend_space_draw->draw_rect(Rect2(Point2(), s), color, false);
+ }
+
+ blend_space_draw->draw_line(Point2(1, s.height - 1), Point2(s.width - 1, s.height - 1), linecolor);
+
+ if (blend_space->get_min_space() < 0) {
+ float point = 0.0;
+ point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space());
+ point *= s.width;
+
+ float x = point;
+
+ blend_space_draw->draw_line(Point2(x, s.height - 1), Point2(x, s.height - 5 * EDSCALE), linecolor);
+ blend_space_draw->draw_string(font, Point2(x + 2 * EDSCALE, s.height - 2 * EDSCALE - font->get_height() + font->get_ascent()), "0", linecolor);
+ blend_space_draw->draw_line(Point2(x, s.height - 5 * EDSCALE), Point2(x, 0), linecolor_soft);
+ }
+
+ if (snap->is_pressed()) {
+
+ linecolor_soft.a = linecolor.a * 0.1;
+
+ if (blend_space->get_snap() > 0) {
+ int prev_idx = -1;
+
+ for (int i = 0; i < s.x; i++) {
+ float v = blend_space->get_min_space() + i * (blend_space->get_max_space() - blend_space->get_min_space()) / s.x;
+ int idx = int(v / blend_space->get_snap());
+
+ if (i > 0 && prev_idx != idx) {
+ blend_space_draw->draw_line(Point2(i, 0), Point2(i, s.height), linecolor_soft);
+ }
+
+ prev_idx = idx;
+ }
+ }
+ }
+
+ points.clear();
+
+ for (int i = 0; i < blend_space->get_blend_point_count(); i++) {
+ float point = blend_space->get_blend_point_position(i);
+
+ if (dragging_selected && selected_point == i) {
+ point += drag_ofs.x;
+ if (snap->is_pressed()) {
+ point = Math::stepify(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);
+
+ gui_point -= (icon->get_size() / 2.0);
+
+ gui_point = gui_point.floor();
+
+ if (i == selected_point) {
+ blend_space_draw->draw_texture(icon_selected, gui_point);
+ } else {
+ blend_space_draw->draw_texture(icon, gui_point);
+ }
+ }
+
+ // blend position
+ {
+ Color color;
+ if (tool_blend->is_pressed()) {
+ color = get_color("accent_color", "Editor");
+ } else {
+ color = linecolor;
+ color.a *= 0.5;
+ }
+
+ float point = blend_space->get_blend_pos();
+ point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space());
+ point *= s.width;
+
+ Vector2 gui_point = Vector2(point, s.height / 2.0);
+
+ float mind = 5 * EDSCALE;
+ float maxd = 15 * EDSCALE;
+ blend_space_draw->draw_line(gui_point + Vector2(mind, 0), gui_point + Vector2(maxd, 0), color, 2);
+ blend_space_draw->draw_line(gui_point + Vector2(-mind, 0), gui_point + Vector2(-maxd, 0), color, 2);
+ blend_space_draw->draw_line(gui_point + Vector2(0, mind), gui_point + Vector2(0, maxd), color, 2);
+ blend_space_draw->draw_line(gui_point + Vector2(0, -mind), gui_point + Vector2(0, -maxd), color, 2);
+ }
+}
+
+void AnimationNodeBlendSpace1DEditor::_update_space() {
+
+ if (updating)
+ return;
+
+ updating = true;
+
+ if (blend_space->get_parent().is_valid()) {
+ goto_parent_hb->show();
+ } else {
+ goto_parent_hb->hide();
+ }
+
+ max_value->set_value(blend_space->get_max_space());
+ min_value->set_value(blend_space->get_min_space());
+
+ label_value->set_text(blend_space->get_value_label());
+
+ snap_value->set_value(blend_space->get_snap());
+
+ blend_space_draw->update();
+
+ updating = false;
+}
+
+void AnimationNodeBlendSpace1DEditor::_config_changed(double) {
+ if (updating)
+ return;
+
+ updating = true;
+ undo_redo->create_action("Change BlendSpace1D Limits");
+ undo_redo->add_do_method(blend_space.ptr(), "set_max_space", max_value->get_value());
+ undo_redo->add_undo_method(blend_space.ptr(), "set_max_space", blend_space->get_max_space());
+ undo_redo->add_do_method(blend_space.ptr(), "set_min_space", min_value->get_value());
+ undo_redo->add_undo_method(blend_space.ptr(), "set_min_space", blend_space->get_min_space());
+ undo_redo->add_do_method(blend_space.ptr(), "set_snap", snap_value->get_value());
+ undo_redo->add_undo_method(blend_space.ptr(), "set_snap", blend_space->get_snap());
+ undo_redo->add_do_method(this, "_update_space");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->commit_action();
+ updating = false;
+
+ blend_space_draw->update();
+}
+
+void AnimationNodeBlendSpace1DEditor::_labels_changed(String) {
+ if (updating)
+ return;
+
+ updating = true;
+ undo_redo->create_action("Change BlendSpace1D Labels", UndoRedo::MERGE_ENDS);
+ undo_redo->add_do_method(blend_space.ptr(), "set_value_label", label_value->get_text());
+ undo_redo->add_undo_method(blend_space.ptr(), "set_value_label", blend_space->get_value_label());
+ undo_redo->add_do_method(this, "_update_space");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->commit_action();
+ updating = false;
+}
+
+void AnimationNodeBlendSpace1DEditor::_snap_toggled() {
+ blend_space_draw->update();
+}
+
+void AnimationNodeBlendSpace1DEditor::_add_menu_type(int p_index) {
+ String type = menu->get_item_metadata(p_index);
+
+ Object *obj = ClassDB::instance(type);
+ ERR_FAIL_COND(!obj);
+ AnimationNode *an = Object::cast_to<AnimationNode>(obj);
+ ERR_FAIL_COND(!an);
+
+ Ref<AnimationNode> node(an);
+
+ updating = true;
+ undo_redo->create_action("Add Node Point");
+ undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", node, add_point_pos);
+ 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");
+ undo_redo->commit_action();
+ updating = false;
+
+ blend_space_draw->update();
+}
+
+void AnimationNodeBlendSpace1DEditor::_add_animation_type(int p_index) {
+ Ref<AnimationNodeAnimation> anim;
+ anim.instance();
+
+ anim->set_animation(animations_to_add[p_index]);
+
+ updating = true;
+ undo_redo->create_action("Add Animation Point");
+ undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", anim, add_point_pos);
+ 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");
+ undo_redo->commit_action();
+ updating = false;
+
+ blend_space_draw->update();
+}
+
+void AnimationNodeBlendSpace1DEditor::_tool_switch(int p_tool) {
+
+ if (p_tool == 0) {
+ tool_erase->show();
+ tool_erase_sep->show();
+ } else {
+ tool_erase->hide();
+ tool_erase_sep->hide();
+ }
+
+ _update_tool_erase();
+ blend_space_draw->update();
+}
+
+void AnimationNodeBlendSpace1DEditor::_update_edited_point_pos() {
+ if (updating)
+ return;
+
+ if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) {
+ float pos = blend_space->get_blend_point_position(selected_point);
+
+ if (dragging_selected) {
+ pos += drag_ofs.x;
+
+ if (snap->is_pressed()) {
+ pos = Math::stepify(pos, blend_space->get_snap());
+ }
+ }
+
+ updating = true;
+ edit_value->set_value(pos);
+ updating = false;
+ }
+}
+
+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);
+
+ if (point_valid) {
+ Ref<AnimationNode> an = blend_space->get_blend_point_node(selected_point);
+
+ if (EditorNode::get_singleton()->item_has_editor(an.ptr())) {
+ open_editor->show();
+ } else {
+ open_editor->hide();
+ }
+
+ edit_hb->show();
+ } else {
+ edit_hb->hide();
+ }
+}
+
+void AnimationNodeBlendSpace1DEditor::_erase_selected() {
+ if (selected_point != -1) {
+ updating = true;
+
+ undo_redo->create_action("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_do_method(this, "_update_space");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->commit_action();
+
+ updating = false;
+
+ blend_space_draw->update();
+ }
+}
+
+void AnimationNodeBlendSpace1DEditor::_edit_point_pos(double) {
+ if (updating)
+ return;
+
+ updating = true;
+ undo_redo->create_action("Move BlendSpace1D Node Point");
+ undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, edit_value->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");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->add_do_method(this, "_update_edited_point_pos");
+ undo_redo->add_undo_method(this, "_update_edited_point_pos");
+ undo_redo->commit_action();
+ updating = false;
+
+ blend_space_draw->update();
+}
+
+void AnimationNodeBlendSpace1DEditor::_open_editor() {
+
+ if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) {
+ Ref<AnimationNode> an = blend_space->get_blend_point_node(selected_point);
+ ERR_FAIL_COND(an.is_null());
+ EditorNode::get_singleton()->edit_item(an.ptr());
+ }
+}
+
+void AnimationNodeBlendSpace1DEditor::_goto_parent() {
+ EditorNode::get_singleton()->edit_item(blend_space->get_parent().ptr());
+}
+
+void AnimationNodeBlendSpace1DEditor::_removed_from_graph() {
+ EditorNode::get_singleton()->edit_item(NULL);
+}
+
+void AnimationNodeBlendSpace1DEditor::_notification(int p_what) {
+ if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
+ error_panel->add_style_override("panel", get_stylebox("bg", "Tree"));
+ error_label->add_color_override("font_color", get_color("error_color", "Editor"));
+ panel->add_style_override("panel", get_stylebox("bg", "Tree"));
+ tool_blend->set_icon(get_icon("EditPivot", "EditorIcons"));
+ tool_select->set_icon(get_icon("ToolSelect", "EditorIcons"));
+ tool_create->set_icon(get_icon("EditKey", "EditorIcons"));
+ tool_erase->set_icon(get_icon("Remove", "EditorIcons"));
+ snap->set_icon(get_icon("SnapGrid", "EditorIcons"));
+ open_editor->set_icon(get_icon("Edit", "EditorIcons"));
+ goto_parent->set_icon(get_icon("MoveUp", "EditorIcons"));
+ }
+
+ if (p_what == NOTIFICATION_PROCESS) {
+ String error;
+
+ if (!blend_space->get_tree()) {
+ error = TTR("BlendSpace1D does not belong to an AnimationTree node.");
+ } else if (!blend_space->get_tree()->is_active()) {
+ error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails.");
+ } else if (blend_space->get_tree()->is_state_invalid()) {
+ error = blend_space->get_tree()->get_invalid_state_reason();
+ }
+
+ if (error != error_label->get_text()) {
+ error_label->set_text(error);
+ if (error != String()) {
+ error_panel->show();
+ } else {
+ error_panel->hide();
+ }
+ }
+ }
+}
+
+void AnimationNodeBlendSpace1DEditor::_bind_methods() {
+ ClassDB::bind_method("_blend_space_gui_input", &AnimationNodeBlendSpace1DEditor::_blend_space_gui_input);
+ ClassDB::bind_method("_blend_space_draw", &AnimationNodeBlendSpace1DEditor::_blend_space_draw);
+ ClassDB::bind_method("_config_changed", &AnimationNodeBlendSpace1DEditor::_config_changed);
+ ClassDB::bind_method("_labels_changed", &AnimationNodeBlendSpace1DEditor::_labels_changed);
+ ClassDB::bind_method("_update_space", &AnimationNodeBlendSpace1DEditor::_update_space);
+ ClassDB::bind_method("_snap_toggled", &AnimationNodeBlendSpace1DEditor::_snap_toggled);
+ ClassDB::bind_method("_tool_switch", &AnimationNodeBlendSpace1DEditor::_tool_switch);
+ ClassDB::bind_method("_erase_selected", &AnimationNodeBlendSpace1DEditor::_erase_selected);
+ ClassDB::bind_method("_update_tool_erase", &AnimationNodeBlendSpace1DEditor::_update_tool_erase);
+ ClassDB::bind_method("_edit_point_pos", &AnimationNodeBlendSpace1DEditor::_edit_point_pos);
+
+ ClassDB::bind_method("_add_menu_type", &AnimationNodeBlendSpace1DEditor::_add_menu_type);
+ ClassDB::bind_method("_add_animation_type", &AnimationNodeBlendSpace1DEditor::_add_animation_type);
+
+ ClassDB::bind_method("_update_edited_point_pos", &AnimationNodeBlendSpace1DEditor::_update_edited_point_pos);
+
+ ClassDB::bind_method("_open_editor", &AnimationNodeBlendSpace1DEditor::_open_editor);
+ ClassDB::bind_method("_goto_parent", &AnimationNodeBlendSpace1DEditor::_goto_parent);
+
+ ClassDB::bind_method("_removed_from_graph", &AnimationNodeBlendSpace1DEditor::_removed_from_graph);
+}
+
+void AnimationNodeBlendSpace1DEditor::edit(AnimationNodeBlendSpace1D *p_blend_space) {
+
+ if (blend_space.is_valid()) {
+ blend_space->disconnect("removed_from_graph", this, "_removed_from_graph");
+ }
+
+ if (p_blend_space) {
+ blend_space = Ref<AnimationNodeBlendSpace1D>(p_blend_space);
+ } else {
+ blend_space.unref();
+ }
+
+ if (blend_space.is_null()) {
+ hide();
+ } else {
+ blend_space->connect("removed_from_graph", this, "_removed_from_graph");
+
+ _update_space();
+ }
+}
+
+AnimationNodeBlendSpace1DEditor *AnimationNodeBlendSpace1DEditor::singleton = NULL;
+
+AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() {
+ singleton = this;
+ updating = false;
+
+ HBoxContainer *top_hb = memnew(HBoxContainer);
+ add_child(top_hb);
+
+ Ref<ButtonGroup> bg;
+ bg.instance();
+
+ goto_parent_hb = memnew(HBoxContainer);
+ top_hb->add_child(goto_parent_hb);
+
+ goto_parent = memnew(ToolButton);
+ goto_parent->connect("pressed", this, "_goto_parent", varray(), CONNECT_DEFERRED);
+ goto_parent_hb->add_child(goto_parent);
+ goto_parent_hb->add_child(memnew(VSeparator));
+ goto_parent_hb->hide();
+
+ tool_blend = memnew(ToolButton);
+ tool_blend->set_toggle_mode(true);
+ tool_blend->set_button_group(bg);
+ top_hb->add_child(tool_blend);
+ tool_blend->set_pressed(true);
+ tool_blend->set_tooltip(TTR("Set the blending position within the space"));
+ tool_blend->connect("pressed", this, "_tool_switch", varray(3));
+
+ tool_select = memnew(ToolButton);
+ tool_select->set_toggle_mode(true);
+ tool_select->set_button_group(bg);
+ top_hb->add_child(tool_select);
+ tool_select->set_tooltip(TTR("Select and move points, create points with RMB."));
+ tool_select->connect("pressed", this, "_tool_switch", varray(0));
+
+ tool_create = memnew(ToolButton);
+ tool_create->set_toggle_mode(true);
+ tool_create->set_button_group(bg);
+ top_hb->add_child(tool_create);
+ tool_create->set_tooltip(TTR("Create points."));
+ tool_create->connect("pressed", this, "_tool_switch", varray(1));
+
+ tool_erase_sep = memnew(VSeparator);
+ top_hb->add_child(tool_erase_sep);
+ tool_erase = memnew(ToolButton);
+ top_hb->add_child(tool_erase);
+ tool_erase->set_tooltip(TTR("Erase points."));
+ tool_erase->connect("pressed", this, "_erase_selected");
+
+ top_hb->add_child(memnew(VSeparator));
+
+ snap = memnew(ToolButton);
+ snap->set_toggle_mode(true);
+ top_hb->add_child(snap);
+ snap->set_pressed(true);
+ snap->connect("pressed", this, "_snap_toggled");
+
+ snap_value = memnew(SpinBox);
+ top_hb->add_child(snap_value);
+ snap_value->set_min(0.01);
+ snap_value->set_step(0.01);
+ snap_value->set_max(1000);
+
+ edit_hb = memnew(HBoxContainer);
+ top_hb->add_child(edit_hb);
+ edit_hb->add_child(memnew(VSeparator));
+ edit_hb->add_child(memnew(Label(TTR("Point"))));
+
+ edit_value = memnew(SpinBox);
+ edit_hb->add_child(edit_value);
+ edit_value->set_min(-1000);
+ edit_value->set_max(1000);
+ edit_value->set_step(0.01);
+ edit_value->connect("value_changed", this, "_edit_point_pos");
+
+ open_editor = memnew(Button);
+ edit_hb->add_child(open_editor);
+ open_editor->set_text(TTR("Open Editor"));
+ open_editor->connect("pressed", this, "_open_editor", varray(), CONNECT_DEFERRED);
+
+ edit_hb->hide();
+ open_editor->hide();
+
+ VBoxContainer *main_vb = memnew(VBoxContainer);
+ add_child(main_vb);
+ main_vb->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ panel = memnew(PanelContainer);
+ panel->set_clip_contents(true);
+ main_vb->add_child(panel);
+ panel->set_h_size_flags(SIZE_EXPAND_FILL);
+ panel->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ blend_space_draw = memnew(Control);
+ blend_space_draw->connect("gui_input", this, "_blend_space_gui_input");
+ blend_space_draw->connect("draw", this, "_blend_space_draw");
+ blend_space_draw->set_focus_mode(FOCUS_ALL);
+
+ panel->add_child(blend_space_draw);
+
+ {
+ HBoxContainer *bottom_hb = memnew(HBoxContainer);
+ main_vb->add_child(bottom_hb);
+ bottom_hb->set_h_size_flags(SIZE_EXPAND_FILL);
+
+ min_value = memnew(SpinBox);
+ min_value->set_max(0);
+ min_value->set_min(-10000);
+ min_value->set_step(0.01);
+
+ max_value = memnew(SpinBox);
+ max_value->set_max(10000);
+ max_value->set_min(0.01);
+ max_value->set_step(0.01);
+
+ label_value = memnew(LineEdit);
+ label_value->set_expand_to_text_length(true);
+
+ // now add
+
+ bottom_hb->add_child(min_value);
+ bottom_hb->add_spacer();
+ bottom_hb->add_child(label_value);
+ bottom_hb->add_spacer();
+ bottom_hb->add_child(max_value);
+ }
+
+ snap_value->connect("value_changed", this, "_config_changed");
+ min_value->connect("value_changed", this, "_config_changed");
+ max_value->connect("value_changed", this, "_config_changed");
+ label_value->connect("text_changed", this, "_labels_changed");
+
+ error_panel = memnew(PanelContainer);
+ add_child(error_panel);
+
+ error_label = memnew(Label);
+ error_panel->add_child(error_label);
+ error_label->set_text("hmmm");
+
+ undo_redo = EditorNode::get_singleton()->get_undo_redo();
+
+ menu = memnew(PopupMenu);
+ add_child(menu);
+ menu->connect("index_pressed", this, "_add_menu_type");
+
+ animations_menu = memnew(PopupMenu);
+ menu->add_child(animations_menu);
+ animations_menu->set_name("animations");
+ animations_menu->connect("index_pressed", this, "_add_animation_type");
+
+ selected_point = -1;
+ dragging_selected = false;
+ dragging_selected_attempt = false;
+
+ set_custom_minimum_size(Size2(0, 150 * EDSCALE));
+}
diff --git a/editor/plugins/animation_blend_space_1d_editor.h b/editor/plugins/animation_blend_space_1d_editor.h
new file mode 100644
index 0000000000..52139626e6
--- /dev/null
+++ b/editor/plugins/animation_blend_space_1d_editor.h
@@ -0,0 +1,117 @@
+#ifndef ANIMATION_BLEND_SPACE_1D_EDITOR_H
+#define ANIMATION_BLEND_SPACE_1D_EDITOR_H
+
+#include "editor/editor_node.h"
+#include "editor/editor_plugin.h"
+#include "editor/property_editor.h"
+#include "scene/animation/animation_blend_space_1d.h"
+#include "scene/gui/button.h"
+#include "scene/gui/graph_edit.h"
+#include "scene/gui/popup.h"
+#include "scene/gui/tree.h"
+
+class AnimationNodeBlendSpace1DEditor : public VBoxContainer {
+
+ GDCLASS(AnimationNodeBlendSpace1DEditor, VBoxContainer)
+
+ Ref<AnimationNodeBlendSpace1D> blend_space;
+
+ HBoxContainer *goto_parent_hb;
+ ToolButton *goto_parent;
+
+ PanelContainer *panel;
+ ToolButton *tool_blend;
+ ToolButton *tool_select;
+ ToolButton *tool_create;
+ VSeparator *tool_erase_sep;
+ ToolButton *tool_erase;
+ ToolButton *snap;
+ SpinBox *snap_value;
+
+ LineEdit *label_value;
+ SpinBox *max_value;
+ SpinBox *min_value;
+
+ HBoxContainer *edit_hb;
+ SpinBox *edit_value;
+ Button *open_editor;
+
+ int selected_point;
+
+ Control *blend_space_draw;
+
+ PanelContainer *error_panel;
+ Label *error_label;
+
+ bool updating;
+
+ UndoRedo *undo_redo;
+
+ static AnimationNodeBlendSpace1DEditor *singleton;
+
+ void _blend_space_gui_input(const Ref<InputEvent> &p_event);
+ void _blend_space_draw();
+
+ void _update_space();
+
+ void _config_changed(double);
+ void _labels_changed(String);
+ void _snap_toggled();
+
+ PopupMenu *menu;
+ PopupMenu *animations_menu;
+ Vector<String> animations_to_add;
+ float add_point_pos;
+ Vector<float> points;
+
+ bool dragging_selected_attempt;
+ bool dragging_selected;
+ Vector2 drag_from;
+ Vector2 drag_ofs;
+
+ 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_tool_erase();
+ void _erase_selected();
+ void _edit_point_pos(double);
+ void _open_editor();
+
+ void _goto_parent();
+
+ void _removed_from_graph();
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ static AnimationNodeBlendSpace1DEditor *get_singleton() { return singleton; }
+ void edit(AnimationNodeBlendSpace1D *p_blend_space);
+ AnimationNodeBlendSpace1DEditor();
+};
+
+class AnimationNodeBlendSpace1DEditorPlugin : public EditorPlugin {
+
+ GDCLASS(AnimationNodeBlendSpace1DEditorPlugin, EditorPlugin)
+
+ AnimationNodeBlendSpace1DEditor *anim_tree_editor;
+ EditorNode *editor;
+ Button *button;
+
+public:
+ virtual String get_name() const { return "BlendSpace1D"; }
+
+ bool has_main_screen() const { return false; }
+
+ virtual void edit(Object *p_object);
+ virtual bool handles(Object *p_object) const;
+ virtual void make_visible(bool p_visible);
+
+ AnimationNodeBlendSpace1DEditorPlugin(EditorNode *p_node);
+ ~AnimationNodeBlendSpace1DEditorPlugin();
+};
+
+#endif // ANIMATION_BLEND_SPACE_1D_EDITOR_H
diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp
new file mode 100644
index 0000000000..8d17062248
--- /dev/null
+++ b/editor/plugins/animation_blend_space_2d_editor.cpp
@@ -0,0 +1,1023 @@
+#include "animation_blend_space_2d_editor.h"
+
+#include "core/io/resource_loader.h"
+#include "core/project_settings.h"
+#include "math/delaunay.h"
+#include "os/input.h"
+#include "os/keyboard.h"
+#include "scene/animation/animation_blend_tree.h"
+#include "scene/animation/animation_player.h"
+#include "scene/gui/menu_button.h"
+#include "scene/gui/panel.h"
+#include "scene/main/viewport.h"
+
+void AnimationNodeBlendSpace2DEditor::edit(AnimationNodeBlendSpace2D *p_blend_space) {
+
+ if (blend_space.is_valid()) {
+ blend_space->disconnect("removed_from_graph", this, "_removed_from_graph");
+ }
+
+ if (p_blend_space) {
+ blend_space = Ref<AnimationNodeBlendSpace2D>(p_blend_space);
+ } else {
+ blend_space.unref();
+ }
+
+ if (blend_space.is_null()) {
+ hide();
+ } else {
+ blend_space->connect("removed_from_graph", this, "_removed_from_graph");
+
+ _update_space();
+ }
+}
+
+void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEvent> &p_event) {
+
+ Ref<InputEventKey> k = p_event;
+ if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_scancode() == KEY_DELETE && !k->is_echo()) {
+ if (selected_point != -1 || selected_triangle != -1) {
+ _erase_selected();
+ accept_event();
+ }
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+
+ if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) || (mb->get_button_index() == BUTTON_LEFT && tool_create->is_pressed()))) {
+ menu->clear();
+ animations_menu->clear();
+ animations_to_add.clear();
+ List<StringName> classes;
+ classes.sort_custom<StringName::AlphCompare>();
+
+ ClassDB::get_inheriters_from_class("AnimationRootNode", &classes);
+ menu->add_submenu_item(TTR("Add Animation"), "animations");
+
+ AnimationTree *gp = blend_space->get_tree();
+ ERR_FAIL_COND(!gp);
+ if (gp && gp->has_node(gp->get_animation_player())) {
+ AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player()));
+ if (ap) {
+ List<StringName> names;
+ ap->get_animation_list(&names);
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ animations_menu->add_icon_item(get_icon("Animation", "EditorIcons"), E->get());
+ animations_to_add.push_back(E->get());
+ }
+ }
+ }
+
+ for (List<StringName>::Element *E = classes.front(); E; E = E->next()) {
+
+ String name = String(E->get()).replace_first("AnimationNode", "");
+ if (name == "Animation")
+ continue; // nope
+ int idx = menu->get_item_count();
+ menu->add_item(vformat("Add %s", name));
+ menu->set_item_metadata(idx, E->get());
+ }
+
+ menu->set_global_position(blend_space_draw->get_global_transform().xform(mb->get_position()));
+ menu->popup();
+ add_point_pos = (mb->get_position() / blend_space_draw->get_size());
+ add_point_pos.y = 1.0 - add_point_pos.y;
+ add_point_pos *= (blend_space->get_max_space() - blend_space->get_min_space());
+ add_point_pos += blend_space->get_min_space();
+
+ if (snap->is_pressed()) {
+ add_point_pos.x = Math::stepify(add_point_pos.x, blend_space->get_snap().x);
+ add_point_pos.y = Math::stepify(add_point_pos.y, blend_space->get_snap().y);
+ }
+ }
+
+ if (mb.is_valid() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
+
+ blend_space_draw->update(); //update anyway
+ //try to see if a point can be selected
+ selected_point = -1;
+ selected_triangle = -1;
+ _update_tool_erase();
+
+ for (int i = 0; i < points.size(); i++) {
+
+ if (points[i].distance_to(mb->get_position()) < 10 * EDSCALE) {
+ selected_point = i;
+ Ref<AnimationNode> node = blend_space->get_blend_point_node(i);
+ EditorNode::get_singleton()->push_item(node.ptr(), "", true);
+ dragging_selected_attempt = true;
+ drag_from = mb->get_position();
+ _update_tool_erase();
+ _update_edited_point_pos();
+ return;
+ }
+ }
+
+ //then try to see if a triangle can be selected
+ if (!blend_space->get_auto_triangles()) { //if autotriangles use, disable this
+ for (int i = 0; i < blend_space->get_triangle_count(); i++) {
+ Vector<Vector2> triangle;
+
+ for (int j = 0; j < 3; j++) {
+ int idx = blend_space->get_triangle_point(i, j);
+ ERR_FAIL_INDEX(idx, points.size());
+ triangle.push_back(points[idx]);
+ }
+
+ if (Geometry::is_point_in_triangle(mb->get_position(), triangle[0], triangle[1], triangle[2])) {
+ selected_triangle = i;
+ _update_tool_erase();
+ return;
+ }
+ }
+ }
+ }
+
+ if (mb.is_valid() && mb->is_pressed() && tool_triangle->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
+
+ blend_space_draw->update(); //update anyway
+ //try to see if a point can be selected
+ selected_point = -1;
+
+ for (int i = 0; i < points.size(); i++) {
+
+ if (making_triangle.find(i) != -1)
+ continue;
+
+ if (points[i].distance_to(mb->get_position()) < 10 * EDSCALE) {
+ making_triangle.push_back(i);
+ if (making_triangle.size() == 3) {
+ //add triangle!
+ if (blend_space->has_triangle(making_triangle[0], making_triangle[1], making_triangle[2])) {
+ making_triangle.clear();
+ EditorNode::get_singleton()->show_warning(TTR("Triangle already exists"));
+ return;
+ }
+
+ updating = true;
+ undo_redo->create_action("Add Triangle");
+ undo_redo->add_do_method(blend_space.ptr(), "add_triangle", making_triangle[0], making_triangle[1], making_triangle[2]);
+ undo_redo->add_undo_method(blend_space.ptr(), "remove_triangle", blend_space->get_triangle_count());
+ undo_redo->add_do_method(this, "_update_space");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->commit_action();
+ updating = false;
+ making_triangle.clear();
+ }
+ return;
+ }
+ }
+ }
+
+ if (mb.is_valid() && !mb->is_pressed() && dragging_selected_attempt && mb->get_button_index() == BUTTON_LEFT) {
+ if (dragging_selected) {
+ //move
+ Vector2 point = blend_space->get_blend_point_position(selected_point);
+ point += drag_ofs;
+ if (snap->is_pressed()) {
+ point.x = Math::stepify(point.x, blend_space->get_snap().x);
+ point.y = Math::stepify(point.y, blend_space->get_snap().y);
+ }
+
+ updating = true;
+ undo_redo->create_action("Move 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");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->add_do_method(this, "_update_edited_point_pos");
+ undo_redo->add_undo_method(this, "_update_edited_point_pos");
+ undo_redo->commit_action();
+ updating = false;
+ _update_edited_point_pos();
+ }
+ dragging_selected_attempt = false;
+ dragging_selected = false;
+ blend_space_draw->update();
+ }
+
+ if (mb.is_valid() && mb->is_pressed() && tool_blend->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
+
+ Vector2 blend_pos = (mb->get_position() / blend_space_draw->get_size());
+ blend_pos.y = 1.0 - blend_pos.y;
+ blend_pos *= (blend_space->get_max_space() - blend_space->get_min_space());
+ blend_pos += blend_space->get_min_space();
+
+ blend_space->set_blend_position(blend_pos);
+ blend_space_draw->update();
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+
+ if (mm.is_valid() && !blend_space_draw->has_focus()) {
+ blend_space_draw->grab_focus();
+ blend_space_draw->update();
+ }
+
+ if (mm.is_valid() && dragging_selected_attempt) {
+ dragging_selected = true;
+ drag_ofs = ((mm->get_position() - drag_from) / blend_space_draw->get_size()) * (blend_space->get_max_space() - blend_space->get_min_space()) * Vector2(1, -1);
+ blend_space_draw->update();
+ _update_edited_point_pos();
+ }
+
+ if (mm.is_valid() && tool_triangle->is_pressed() && making_triangle.size()) {
+ blend_space_draw->update();
+ }
+
+ if (mm.is_valid() && !tool_triangle->is_pressed() && making_triangle.size()) {
+ making_triangle.clear();
+ blend_space_draw->update();
+ }
+
+ if (mm.is_valid() && tool_blend->is_pressed() && mm->get_button_mask() & BUTTON_MASK_LEFT) {
+
+ Vector2 blend_pos = (mm->get_position() / blend_space_draw->get_size());
+ blend_pos.y = 1.0 - blend_pos.y;
+ blend_pos *= (blend_space->get_max_space() - blend_space->get_min_space());
+ blend_pos += blend_space->get_min_space();
+
+ blend_space->set_blend_position(blend_pos);
+ blend_space_draw->update();
+ }
+}
+
+void AnimationNodeBlendSpace2DEditor::_add_menu_type(int p_index) {
+
+ String type = menu->get_item_metadata(p_index);
+
+ Object *obj = ClassDB::instance(type);
+ ERR_FAIL_COND(!obj);
+ AnimationNode *an = Object::cast_to<AnimationNode>(obj);
+ ERR_FAIL_COND(!an);
+
+ Ref<AnimationNode> node(an);
+
+ updating = true;
+ undo_redo->create_action("Add Node Point");
+ undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", node, add_point_pos);
+ 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");
+ undo_redo->commit_action();
+ updating = false;
+
+ blend_space_draw->update();
+}
+
+void AnimationNodeBlendSpace2DEditor::_add_animation_type(int p_index) {
+
+ Ref<AnimationNodeAnimation> anim;
+ anim.instance();
+
+ anim->set_animation(animations_to_add[p_index]);
+
+ updating = true;
+ undo_redo->create_action("Add Animation Point");
+ undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", anim, add_point_pos);
+ 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");
+ undo_redo->commit_action();
+ updating = false;
+
+ blend_space_draw->update();
+}
+
+void AnimationNodeBlendSpace2DEditor::_update_tool_erase() {
+ tool_erase->set_disabled(!(selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) && !(selected_triangle >= 0 && selected_triangle < blend_space->get_triangle_count()));
+ if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) {
+ Ref<AnimationNode> an = blend_space->get_blend_point_node(selected_point);
+ if (EditorNode::get_singleton()->item_has_editor(an.ptr())) {
+ open_editor->show();
+ } else {
+ open_editor->hide();
+ }
+ edit_hb->show();
+ } else {
+ edit_hb->hide();
+ }
+}
+
+void AnimationNodeBlendSpace2DEditor::_tool_switch(int p_tool) {
+ making_triangle.clear();
+
+ if (p_tool == 2) {
+ Vector<Vector2> points;
+ for (int i = 0; i < blend_space->get_blend_point_count(); i++) {
+ points.push_back(blend_space->get_blend_point_position(i));
+ }
+ Vector<Delaunay2D::Triangle> tr = Delaunay2D::triangulate(points);
+ print_line("triangleS: " + itos(tr.size()));
+ for (int i = 0; i < tr.size(); i++) {
+ blend_space->add_triangle(tr[i].points[0], tr[i].points[1], tr[i].points[2]);
+ }
+ }
+
+ if (p_tool == 0) {
+ tool_erase->show();
+ tool_erase_sep->show();
+ } else {
+ tool_erase->hide();
+ tool_erase_sep->hide();
+ }
+ _update_tool_erase();
+ blend_space_draw->update();
+}
+
+void AnimationNodeBlendSpace2DEditor::_blend_space_draw() {
+
+ Color linecolor = get_color("font_color", "Label");
+ Color linecolor_soft = linecolor;
+ linecolor_soft.a *= 0.5;
+ Ref<Font> font = get_font("font", "Label");
+ Ref<Texture> icon = get_icon("KeyValue", "EditorIcons");
+ Ref<Texture> icon_selected = get_icon("KeySelected", "EditorIcons");
+
+ Size2 s = blend_space_draw->get_size();
+
+ if (blend_space_draw->has_focus()) {
+ Color color = get_color("accent_color", "Editor");
+ blend_space_draw->draw_rect(Rect2(Point2(), s), color, false);
+ }
+ blend_space_draw->draw_line(Point2(1, 0), Point2(1, s.height - 1), linecolor);
+ blend_space_draw->draw_line(Point2(1, s.height - 1), Point2(s.width - 1, s.height - 1), linecolor);
+
+ blend_space_draw->draw_line(Point2(0, 0), Point2(5 * EDSCALE, 0), linecolor);
+ if (blend_space->get_min_space().y < 0) {
+ int y = (blend_space->get_max_space().y / (blend_space->get_max_space().y - blend_space->get_min_space().y)) * s.height;
+ blend_space_draw->draw_line(Point2(0, y), Point2(5 * EDSCALE, y), linecolor);
+ blend_space_draw->draw_string(font, Point2(2 * EDSCALE, y - font->get_height() + font->get_ascent()), "0", linecolor);
+ blend_space_draw->draw_line(Point2(5 * EDSCALE, y), Point2(s.width, y), linecolor_soft);
+ }
+
+ if (blend_space->get_min_space().x < 0) {
+ int x = (-blend_space->get_min_space().x / (blend_space->get_max_space().x - blend_space->get_min_space().x)) * s.width;
+ blend_space_draw->draw_line(Point2(x, s.height - 1), Point2(x, s.height - 5 * EDSCALE), linecolor);
+ blend_space_draw->draw_string(font, Point2(x + 2 * EDSCALE, s.height - 2 * EDSCALE - font->get_height() + font->get_ascent()), "0", linecolor);
+ blend_space_draw->draw_line(Point2(x, s.height - 5 * EDSCALE), Point2(x, 0), linecolor_soft);
+ }
+
+ if (snap->is_pressed()) {
+
+ linecolor_soft.a = linecolor.a * 0.1;
+
+ if (blend_space->get_snap().x > 0) {
+
+ int prev_idx;
+ for (int i = 0; i < s.x; i++) {
+
+ float v = blend_space->get_min_space().x + i * (blend_space->get_max_space().x - blend_space->get_min_space().x) / s.x;
+ int idx = int(v / blend_space->get_snap().x);
+
+ if (i > 0 && prev_idx != idx) {
+ blend_space_draw->draw_line(Point2(i, 0), Point2(i, s.height), linecolor_soft);
+ }
+
+ prev_idx = idx;
+ }
+ }
+
+ if (blend_space->get_snap().y > 0) {
+
+ int prev_idx;
+ for (int i = 0; i < s.y; i++) {
+
+ float v = blend_space->get_max_space().y - i * (blend_space->get_max_space().y - blend_space->get_min_space().y) / s.y;
+ int idx = int(v / blend_space->get_snap().y);
+
+ if (i > 0 && prev_idx != idx) {
+ blend_space_draw->draw_line(Point2(0, i), Point2(s.width, i), linecolor_soft);
+ }
+
+ prev_idx = idx;
+ }
+ }
+ }
+
+ //triangles first
+ for (int i = 0; i < blend_space->get_triangle_count(); i++) {
+
+ Vector<Vector2> points;
+ points.resize(3);
+
+ for (int j = 0; j < 3; j++) {
+ int point_idx = blend_space->get_triangle_point(i, j);
+ Vector2 point = blend_space->get_blend_point_position(point_idx);
+ if (dragging_selected && selected_point == point_idx) {
+ point += drag_ofs;
+ if (snap->is_pressed()) {
+ point.x = Math::stepify(point.x, blend_space->get_snap().x);
+ point.y = Math::stepify(point.y, blend_space->get_snap().y);
+ }
+ }
+ 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[j] = point;
+ }
+
+ for (int j = 0; j < 3; j++) {
+ blend_space_draw->draw_line(points[j], points[(j + 1) % 3], linecolor, 1, true);
+ }
+
+ Color color;
+ if (i == selected_triangle) {
+ color = get_color("accent_color", "Editor");
+ color.a *= 0.5;
+ } else {
+ color = linecolor;
+ color.a *= 0.2;
+ }
+
+ Vector<Color> colors;
+ colors.push_back(color);
+ colors.push_back(color);
+ colors.push_back(color);
+ blend_space_draw->draw_primitive(points, colors, Vector<Vector2>());
+ }
+
+ points.clear();
+ for (int i = 0; i < blend_space->get_blend_point_count(); i++) {
+
+ Vector2 point = blend_space->get_blend_point_position(i);
+ if (dragging_selected && selected_point == i) {
+ point += drag_ofs;
+ if (snap->is_pressed()) {
+ point.x = Math::stepify(point.x, blend_space->get_snap().x);
+ point.y = Math::stepify(point.y, blend_space->get_snap().y);
+ }
+ }
+ 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);
+ }
+ }
+
+ if (making_triangle.size()) {
+ Vector<Vector2> points;
+ for (int i = 0; i < making_triangle.size(); i++) {
+ Vector2 point = blend_space->get_blend_point_position(making_triangle[i]);
+ 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);
+ }
+
+ for (int i = 0; i < points.size() - 1; i++) {
+ blend_space_draw->draw_line(points[i], points[i + 1], linecolor, 2, true);
+ }
+ blend_space_draw->draw_line(points[points.size() - 1], blend_space_draw->get_local_mouse_position(), linecolor, 2, true);
+ }
+
+ ///draw cursor position
+
+ {
+ Color color;
+ if (tool_blend->is_pressed()) {
+ color = get_color("accent_color", "Editor");
+ } else {
+ color = linecolor;
+ color.a *= 0.5;
+ }
+
+ Vector2 point = blend_space->get_blend_position();
+ 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;
+
+ if (blend_space->get_triangle_count()) {
+ Vector2 closest = blend_space->get_closest_point(blend_space->get_blend_position());
+ closest = (closest - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space());
+ closest *= s;
+ closest.y = s.height - closest.y;
+
+ Color lcol = color;
+ lcol.a *= 0.4;
+ blend_space_draw->draw_line(point, closest, lcol, 2);
+ }
+
+ float mind = 5 * EDSCALE;
+ float maxd = 15 * EDSCALE;
+ blend_space_draw->draw_line(point + Vector2(mind, 0), point + Vector2(maxd, 0), color, 2);
+ blend_space_draw->draw_line(point + Vector2(-mind, 0), point + Vector2(-maxd, 0), color, 2);
+ blend_space_draw->draw_line(point + Vector2(0, mind), point + Vector2(0, maxd), color, 2);
+ blend_space_draw->draw_line(point + Vector2(0, -mind), point + Vector2(0, -maxd), color, 2);
+ }
+}
+
+void AnimationNodeBlendSpace2DEditor::_snap_toggled() {
+
+ blend_space_draw->update();
+}
+
+void AnimationNodeBlendSpace2DEditor::_update_space() {
+
+ if (updating)
+ return;
+
+ updating = true;
+
+ if (blend_space->get_parent().is_valid()) {
+ goto_parent_hb->show();
+ } else {
+ goto_parent_hb->hide();
+ }
+
+ if (blend_space->get_auto_triangles()) {
+ tool_triangle->hide();
+ } else {
+ tool_triangle->show();
+ }
+
+ auto_triangles->set_pressed(blend_space->get_auto_triangles());
+
+ max_x_value->set_value(blend_space->get_max_space().x);
+ max_y_value->set_value(blend_space->get_max_space().y);
+
+ min_x_value->set_value(blend_space->get_min_space().x);
+ min_y_value->set_value(blend_space->get_min_space().y);
+
+ label_x->set_text(blend_space->get_x_label());
+ label_y->set_text(blend_space->get_y_label());
+
+ snap_x->set_value(blend_space->get_snap().x);
+ snap_y->set_value(blend_space->get_snap().y);
+
+ blend_space_draw->update();
+
+ updating = false;
+}
+
+void AnimationNodeBlendSpace2DEditor::_config_changed(double) {
+ if (updating)
+ return;
+
+ updating = true;
+ undo_redo->create_action("Change BlendSpace2D Limits");
+ undo_redo->add_do_method(blend_space.ptr(), "set_max_space", Vector2(max_x_value->get_value(), max_y_value->get_value()));
+ undo_redo->add_undo_method(blend_space.ptr(), "set_max_space", blend_space->get_max_space());
+ undo_redo->add_do_method(blend_space.ptr(), "set_min_space", Vector2(min_x_value->get_value(), min_y_value->get_value()));
+ undo_redo->add_undo_method(blend_space.ptr(), "set_min_space", blend_space->get_min_space());
+ undo_redo->add_do_method(blend_space.ptr(), "set_snap", Vector2(snap_x->get_value(), snap_y->get_value()));
+ undo_redo->add_undo_method(blend_space.ptr(), "set_snap", blend_space->get_snap());
+ undo_redo->add_do_method(this, "_update_space");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->commit_action();
+ updating = false;
+
+ blend_space_draw->update();
+}
+
+void AnimationNodeBlendSpace2DEditor::_labels_changed(String) {
+ if (updating)
+ return;
+
+ updating = true;
+ undo_redo->create_action("Change BlendSpace2D Labels", UndoRedo::MERGE_ENDS);
+ undo_redo->add_do_method(blend_space.ptr(), "set_x_label", label_x->get_text());
+ undo_redo->add_undo_method(blend_space.ptr(), "set_x_label", blend_space->get_x_label());
+ undo_redo->add_do_method(blend_space.ptr(), "set_y_label", label_y->get_text());
+ undo_redo->add_undo_method(blend_space.ptr(), "set_y_label", blend_space->get_y_label());
+ undo_redo->add_do_method(this, "_update_space");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->commit_action();
+ updating = false;
+}
+
+void AnimationNodeBlendSpace2DEditor::_erase_selected() {
+
+ if (selected_point != -1) {
+
+ updating = true;
+ undo_redo->create_action("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);
+
+ //restore triangles using this point
+ for (int i = 0; i < blend_space->get_triangle_count(); i++) {
+ for (int j = 0; j < 3; j++) {
+ if (blend_space->get_triangle_point(i, j) == selected_point) {
+ undo_redo->add_undo_method(blend_space.ptr(), "add_triangle", blend_space->get_triangle_point(i, 0), blend_space->get_triangle_point(i, 1), blend_space->get_triangle_point(i, 2), i);
+ break;
+ }
+ }
+ }
+
+ undo_redo->add_do_method(this, "_update_space");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->commit_action();
+ updating = false;
+
+ blend_space_draw->update();
+ } else if (selected_triangle != -1) {
+
+ updating = true;
+ undo_redo->create_action("Remove BlendSpace2D Triangle");
+ undo_redo->add_do_method(blend_space.ptr(), "remove_triangle", selected_triangle);
+ undo_redo->add_undo_method(blend_space.ptr(), "add_triangle", blend_space->get_triangle_point(selected_triangle, 0), blend_space->get_triangle_point(selected_triangle, 1), blend_space->get_triangle_point(selected_triangle, 2), selected_triangle);
+
+ undo_redo->add_do_method(this, "_update_space");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->commit_action();
+ updating = false;
+
+ blend_space_draw->update();
+ }
+}
+
+void AnimationNodeBlendSpace2DEditor::_update_edited_point_pos() {
+ if (updating)
+ return;
+
+ if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) {
+ Vector2 pos = blend_space->get_blend_point_position(selected_point);
+ if (dragging_selected) {
+ pos += drag_ofs;
+ if (snap->is_pressed()) {
+ pos.x = Math::stepify(pos.x, blend_space->get_snap().x);
+ pos.y = Math::stepify(pos.y, blend_space->get_snap().y);
+ }
+ }
+ updating = true;
+ edit_x->set_value(pos.x);
+ edit_y->set_value(pos.y);
+ updating = false;
+ }
+}
+
+void AnimationNodeBlendSpace2DEditor::_edit_point_pos(double) {
+ if (updating)
+ return;
+ updating = true;
+ undo_redo->create_action("Move 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");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->add_do_method(this, "_update_edited_point_pos");
+ undo_redo->add_undo_method(this, "_update_edited_point_pos");
+ undo_redo->commit_action();
+ updating = false;
+
+ blend_space_draw->update();
+}
+
+void AnimationNodeBlendSpace2DEditor::_notification(int p_what) {
+
+ if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
+ error_panel->add_style_override("panel", get_stylebox("bg", "Tree"));
+ error_label->add_color_override("font_color", get_color("error_color", "Editor"));
+ panel->add_style_override("panel", get_stylebox("bg", "Tree"));
+ tool_blend->set_icon(get_icon("EditPivot", "EditorIcons"));
+ tool_select->set_icon(get_icon("ToolSelect", "EditorIcons"));
+ tool_create->set_icon(get_icon("EditKey", "EditorIcons"));
+ tool_triangle->set_icon(get_icon("ToolTriangle", "EditorIcons"));
+ tool_erase->set_icon(get_icon("Remove", "EditorIcons"));
+ snap->set_icon(get_icon("SnapGrid", "EditorIcons"));
+ open_editor->set_icon(get_icon("Edit", "EditorIcons"));
+ goto_parent->set_icon(get_icon("MoveUp", "EditorIcons"));
+ auto_triangles->set_icon(get_icon("AutoTriangle", "EditorIcons"));
+ }
+
+ if (p_what == NOTIFICATION_PROCESS) {
+
+ String error;
+
+ if (!blend_space->get_tree()) {
+ error = TTR("BlendSpace2D does not belong to an AnimationTree node.");
+ } else if (!blend_space->get_tree()->is_active()) {
+ error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails.");
+ } else if (blend_space->get_tree()->is_state_invalid()) {
+ error = blend_space->get_tree()->get_invalid_state_reason();
+ } else if (blend_space->get_triangle_count() == 0) {
+ error = TTR("No triangles exist, so no blending can take place.");
+ }
+
+ if (error != error_label->get_text()) {
+ error_label->set_text(error);
+ if (error != String()) {
+ error_panel->show();
+ } else {
+ error_panel->hide();
+ }
+ }
+ }
+}
+
+void AnimationNodeBlendSpace2DEditor::_open_editor() {
+
+ if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) {
+ Ref<AnimationNode> an = blend_space->get_blend_point_node(selected_point);
+ ERR_FAIL_COND(!an.is_valid());
+ EditorNode::get_singleton()->edit_item(an.ptr());
+ }
+}
+
+void AnimationNodeBlendSpace2DEditor::_goto_parent() {
+
+ EditorNode::get_singleton()->edit_item(blend_space->get_parent().ptr());
+}
+
+void AnimationNodeBlendSpace2DEditor::_removed_from_graph() {
+ EditorNode::get_singleton()->edit_item(NULL);
+}
+
+void AnimationNodeBlendSpace2DEditor::_auto_triangles_toggled() {
+
+ undo_redo->create_action("Toggle Auto Triangles");
+ undo_redo->add_do_method(blend_space.ptr(), "set_auto_triangles", auto_triangles->is_pressed());
+ undo_redo->add_undo_method(blend_space.ptr(), "set_auto_triangles", blend_space->get_auto_triangles());
+ undo_redo->add_do_method(this, "_update_space");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->commit_action();
+}
+
+void AnimationNodeBlendSpace2DEditor::_bind_methods() {
+
+ ClassDB::bind_method("_blend_space_gui_input", &AnimationNodeBlendSpace2DEditor::_blend_space_gui_input);
+ ClassDB::bind_method("_blend_space_draw", &AnimationNodeBlendSpace2DEditor::_blend_space_draw);
+ ClassDB::bind_method("_config_changed", &AnimationNodeBlendSpace2DEditor::_config_changed);
+ ClassDB::bind_method("_labels_changed", &AnimationNodeBlendSpace2DEditor::_labels_changed);
+ ClassDB::bind_method("_update_space", &AnimationNodeBlendSpace2DEditor::_update_space);
+ ClassDB::bind_method("_snap_toggled", &AnimationNodeBlendSpace2DEditor::_snap_toggled);
+ ClassDB::bind_method("_tool_switch", &AnimationNodeBlendSpace2DEditor::_tool_switch);
+ ClassDB::bind_method("_erase_selected", &AnimationNodeBlendSpace2DEditor::_erase_selected);
+ ClassDB::bind_method("_update_tool_erase", &AnimationNodeBlendSpace2DEditor::_update_tool_erase);
+ ClassDB::bind_method("_edit_point_pos", &AnimationNodeBlendSpace2DEditor::_edit_point_pos);
+
+ ClassDB::bind_method("_add_menu_type", &AnimationNodeBlendSpace2DEditor::_add_menu_type);
+ ClassDB::bind_method("_add_animation_type", &AnimationNodeBlendSpace2DEditor::_add_animation_type);
+
+ ClassDB::bind_method("_update_edited_point_pos", &AnimationNodeBlendSpace2DEditor::_update_edited_point_pos);
+
+ ClassDB::bind_method("_open_editor", &AnimationNodeBlendSpace2DEditor::_open_editor);
+ ClassDB::bind_method("_goto_parent", &AnimationNodeBlendSpace2DEditor::_goto_parent);
+
+ ClassDB::bind_method("_removed_from_graph", &AnimationNodeBlendSpace2DEditor::_removed_from_graph);
+
+ ClassDB::bind_method("_auto_triangles_toggled", &AnimationNodeBlendSpace2DEditor::_auto_triangles_toggled);
+}
+
+AnimationNodeBlendSpace2DEditor *AnimationNodeBlendSpace2DEditor::singleton = NULL;
+
+AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() {
+
+ singleton = this;
+ updating = false;
+
+ HBoxContainer *top_hb = memnew(HBoxContainer);
+ add_child(top_hb);
+
+ Ref<ButtonGroup> bg;
+ bg.instance();
+
+ goto_parent_hb = memnew(HBoxContainer);
+ top_hb->add_child(goto_parent_hb);
+ goto_parent = memnew(ToolButton);
+ goto_parent->connect("pressed", this, "_goto_parent", varray(), CONNECT_DEFERRED);
+ goto_parent_hb->add_child(goto_parent);
+ goto_parent_hb->add_child(memnew(VSeparator));
+ goto_parent_hb->hide();
+
+ tool_blend = memnew(ToolButton);
+ tool_blend->set_toggle_mode(true);
+ tool_blend->set_button_group(bg);
+ top_hb->add_child(tool_blend);
+ tool_blend->set_pressed(true);
+ tool_blend->set_tooltip(TTR("Set the blending position within the space"));
+ tool_blend->connect("pressed", this, "_tool_switch", varray(3));
+
+ tool_select = memnew(ToolButton);
+ tool_select->set_toggle_mode(true);
+ tool_select->set_button_group(bg);
+ top_hb->add_child(tool_select);
+ tool_select->set_tooltip(TTR("Select and move points, create points with RMB."));
+ tool_select->connect("pressed", this, "_tool_switch", varray(0));
+
+ tool_create = memnew(ToolButton);
+ tool_create->set_toggle_mode(true);
+ tool_create->set_button_group(bg);
+ top_hb->add_child(tool_create);
+ tool_create->set_tooltip(TTR("Create points."));
+ tool_create->connect("pressed", this, "_tool_switch", varray(1));
+
+ tool_triangle = memnew(ToolButton);
+ tool_triangle->set_toggle_mode(true);
+ tool_triangle->set_button_group(bg);
+ top_hb->add_child(tool_triangle);
+ tool_triangle->set_tooltip(TTR("Create triangles by connecting points."));
+ tool_triangle->connect("pressed", this, "_tool_switch", varray(2));
+
+ tool_erase_sep = memnew(VSeparator);
+ top_hb->add_child(tool_erase_sep);
+ tool_erase = memnew(ToolButton);
+ top_hb->add_child(tool_erase);
+ tool_erase->set_tooltip(TTR("Erase points and triangles."));
+ tool_erase->connect("pressed", this, "_erase_selected");
+ tool_erase->set_disabled(true);
+
+ top_hb->add_child(memnew(VSeparator));
+
+ auto_triangles = memnew(ToolButton);
+ top_hb->add_child(auto_triangles);
+ auto_triangles->connect("pressed", this, "_auto_triangles_toggled");
+ auto_triangles->set_toggle_mode(true);
+ auto_triangles->set_tooltip(TTR("Generate blend triangles automatically (instead of manually)"));
+
+ top_hb->add_child(memnew(VSeparator));
+
+ snap = memnew(ToolButton);
+ snap->set_toggle_mode(true);
+ top_hb->add_child(snap);
+ //snap->set_text(TTR("Snap"));
+ snap->set_pressed(true);
+ snap->connect("pressed", this, "_snap_toggled");
+
+ snap_x = memnew(SpinBox);
+ top_hb->add_child(snap_x);
+ snap_x->set_prefix("x:");
+ snap_x->set_min(0.01);
+ snap_x->set_step(0.01);
+ snap_x->set_max(1000);
+
+ snap_y = memnew(SpinBox);
+ top_hb->add_child(snap_y);
+ snap_y->set_prefix("y:");
+ snap_y->set_min(0.01);
+ snap_y->set_step(0.01);
+ snap_y->set_max(1000);
+
+ edit_hb = memnew(HBoxContainer);
+ top_hb->add_child(edit_hb);
+ edit_hb->add_child(memnew(VSeparator));
+ edit_hb->add_child(memnew(Label(TTR("Point"))));
+ edit_x = memnew(SpinBox);
+ edit_hb->add_child(edit_x);
+ edit_x->set_min(-1000);
+ edit_x->set_step(0.01);
+ edit_x->set_max(1000);
+ edit_x->connect("value_changed", this, "_edit_point_pos");
+ edit_y = memnew(SpinBox);
+ edit_hb->add_child(edit_y);
+ edit_y->set_min(-1000);
+ edit_y->set_step(0.01);
+ edit_y->set_max(1000);
+ edit_y->connect("value_changed", this, "_edit_point_pos");
+ open_editor = memnew(Button);
+ edit_hb->add_child(open_editor);
+ open_editor->set_text(TTR("Open Editor"));
+ open_editor->connect("pressed", this, "_open_editor", varray(), CONNECT_DEFERRED);
+ edit_hb->hide();
+ open_editor->hide();
+
+ HBoxContainer *main_hb = memnew(HBoxContainer);
+ add_child(main_hb);
+ main_hb->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ GridContainer *main_grid = memnew(GridContainer);
+ main_grid->set_columns(2);
+ main_hb->add_child(main_grid);
+ main_grid->set_h_size_flags(SIZE_EXPAND_FILL);
+ {
+ VBoxContainer *left_vbox = memnew(VBoxContainer);
+ main_grid->add_child(left_vbox);
+ left_vbox->set_v_size_flags(SIZE_EXPAND_FILL);
+ max_y_value = memnew(SpinBox);
+ left_vbox->add_child(max_y_value);
+ left_vbox->add_spacer();
+ label_y = memnew(LineEdit);
+ left_vbox->add_child(label_y);
+ label_y->set_expand_to_text_length(true);
+ left_vbox->add_spacer();
+ min_y_value = memnew(SpinBox);
+ left_vbox->add_child(min_y_value);
+
+ max_y_value->set_max(10000);
+ max_y_value->set_min(0.01);
+ max_y_value->set_step(0.01);
+
+ min_y_value->set_min(-10000);
+ min_y_value->set_max(0);
+ min_y_value->set_step(0.01);
+ }
+
+ panel = memnew(PanelContainer);
+ panel->set_clip_contents(true);
+ main_grid->add_child(panel);
+ panel->set_h_size_flags(SIZE_EXPAND_FILL);
+
+ blend_space_draw = memnew(Control);
+ blend_space_draw->connect("gui_input", this, "_blend_space_gui_input");
+ blend_space_draw->connect("draw", this, "_blend_space_draw");
+ blend_space_draw->set_focus_mode(FOCUS_ALL);
+
+ panel->add_child(blend_space_draw);
+ main_grid->add_child(memnew(Control)); //empty bottom left
+
+ {
+ HBoxContainer *bottom_vbox = memnew(HBoxContainer);
+ main_grid->add_child(bottom_vbox);
+ bottom_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
+ min_x_value = memnew(SpinBox);
+ bottom_vbox->add_child(min_x_value);
+ bottom_vbox->add_spacer();
+ label_x = memnew(LineEdit);
+ bottom_vbox->add_child(label_x);
+ label_x->set_expand_to_text_length(true);
+ bottom_vbox->add_spacer();
+ max_x_value = memnew(SpinBox);
+ bottom_vbox->add_child(max_x_value);
+
+ max_x_value->set_max(10000);
+ max_x_value->set_min(0.01);
+ max_x_value->set_step(0.01);
+
+ min_x_value->set_min(-10000);
+ min_x_value->set_max(0);
+ min_x_value->set_step(0.01);
+ }
+
+ snap_x->connect("value_changed", this, "_config_changed");
+ snap_y->connect("value_changed", this, "_config_changed");
+ max_x_value->connect("value_changed", this, "_config_changed");
+ min_x_value->connect("value_changed", this, "_config_changed");
+ max_y_value->connect("value_changed", this, "_config_changed");
+ min_y_value->connect("value_changed", this, "_config_changed");
+ label_x->connect("text_changed", this, "_labels_changed");
+ label_y->connect("text_changed", this, "_labels_changed");
+
+ error_panel = memnew(PanelContainer);
+ add_child(error_panel);
+ error_label = memnew(Label);
+ error_panel->add_child(error_label);
+ error_label->set_text("eh");
+
+ undo_redo = EditorNode::get_singleton()->get_undo_redo();
+
+ set_custom_minimum_size(Size2(0, 300 * EDSCALE));
+
+ menu = memnew(PopupMenu);
+ add_child(menu);
+ menu->connect("index_pressed", this, "_add_menu_type");
+
+ animations_menu = memnew(PopupMenu);
+ menu->add_child(animations_menu);
+ animations_menu->set_name("animations");
+ animations_menu->connect("index_pressed", this, "_add_animation_type");
+
+ selected_point = -1;
+ selected_triangle = -1;
+
+ dragging_selected = false;
+ dragging_selected_attempt = false;
+}
+
+void AnimationNodeBlendSpace2DEditorPlugin::edit(Object *p_object) {
+
+ anim_tree_editor->edit(Object::cast_to<AnimationNodeBlendSpace2D>(p_object));
+}
+
+bool AnimationNodeBlendSpace2DEditorPlugin::handles(Object *p_object) const {
+
+ return p_object->is_class("AnimationNodeBlendSpace2D");
+}
+
+void AnimationNodeBlendSpace2DEditorPlugin::make_visible(bool p_visible) {
+
+ if (p_visible) {
+ //editor->hide_animation_player_editors();
+ //editor->animation_panel_make_visible(true);
+ button->show();
+ editor->make_bottom_panel_item_visible(anim_tree_editor);
+ anim_tree_editor->set_process(true);
+ } else {
+
+ if (anim_tree_editor->is_visible_in_tree())
+ editor->hide_bottom_panel();
+ button->hide();
+ anim_tree_editor->set_process(false);
+ }
+}
+
+AnimationNodeBlendSpace2DEditorPlugin::AnimationNodeBlendSpace2DEditorPlugin(EditorNode *p_node) {
+
+ editor = p_node;
+ anim_tree_editor = memnew(AnimationNodeBlendSpace2DEditor);
+ anim_tree_editor->set_custom_minimum_size(Size2(0, 300));
+
+ button = editor->add_bottom_panel_item(TTR("BlendSpace2D"), anim_tree_editor);
+ button->hide();
+}
+
+AnimationNodeBlendSpace2DEditorPlugin::~AnimationNodeBlendSpace2DEditorPlugin() {
+}
diff --git a/editor/plugins/animation_blend_space_2d_editor.h b/editor/plugins/animation_blend_space_2d_editor.h
new file mode 100644
index 0000000000..a0e497804e
--- /dev/null
+++ b/editor/plugins/animation_blend_space_2d_editor.h
@@ -0,0 +1,130 @@
+#ifndef ANIMATION_BLEND_SPACE_2D_EDITOR_H
+#define ANIMATION_BLEND_SPACE_2D_EDITOR_H
+
+#include "editor/editor_node.h"
+#include "editor/editor_plugin.h"
+#include "editor/property_editor.h"
+#include "scene/animation/animation_blend_space_2d.h"
+#include "scene/gui/button.h"
+#include "scene/gui/graph_edit.h"
+#include "scene/gui/popup.h"
+#include "scene/gui/tree.h"
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+
+class AnimationNodeBlendSpace2DEditor : public VBoxContainer {
+
+ GDCLASS(AnimationNodeBlendSpace2DEditor, VBoxContainer);
+
+ Ref<AnimationNodeBlendSpace2D> blend_space;
+
+ HBoxContainer *goto_parent_hb;
+ ToolButton *goto_parent;
+
+ PanelContainer *panel;
+ ToolButton *tool_blend;
+ ToolButton *tool_select;
+ ToolButton *tool_create;
+ ToolButton *tool_triangle;
+ VSeparator *tool_erase_sep;
+ ToolButton *tool_erase;
+ ToolButton *snap;
+ SpinBox *snap_x;
+ SpinBox *snap_y;
+
+ ToolButton *auto_triangles;
+
+ LineEdit *label_x;
+ LineEdit *label_y;
+ SpinBox *max_x_value;
+ SpinBox *min_x_value;
+ SpinBox *max_y_value;
+ SpinBox *min_y_value;
+
+ HBoxContainer *edit_hb;
+ SpinBox *edit_x;
+ SpinBox *edit_y;
+ Button *open_editor;
+
+ int selected_point;
+ int selected_triangle;
+
+ Control *blend_space_draw;
+
+ PanelContainer *error_panel;
+ Label *error_label;
+
+ bool updating;
+
+ UndoRedo *undo_redo;
+
+ static AnimationNodeBlendSpace2DEditor *singleton;
+
+ void _blend_space_gui_input(const Ref<InputEvent> &p_event);
+ void _blend_space_draw();
+
+ void _update_space();
+
+ void _config_changed(double);
+ void _labels_changed(String);
+ void _snap_toggled();
+
+ PopupMenu *menu;
+ PopupMenu *animations_menu;
+ Vector<String> animations_to_add;
+ Vector2 add_point_pos;
+ Vector<Vector2> points;
+
+ bool dragging_selected_attempt;
+ bool dragging_selected;
+ Vector2 drag_from;
+ Vector2 drag_ofs;
+
+ Vector<int> making_triangle;
+
+ 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_tool_erase();
+ void _erase_selected();
+ void _edit_point_pos(double);
+ void _open_editor();
+
+ void _goto_parent();
+
+ void _removed_from_graph();
+
+ void _auto_triangles_toggled();
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ static AnimationNodeBlendSpace2DEditor *get_singleton() { return singleton; }
+ void edit(AnimationNodeBlendSpace2D *p_blend_space);
+ AnimationNodeBlendSpace2DEditor();
+};
+
+class AnimationNodeBlendSpace2DEditorPlugin : public EditorPlugin {
+
+ GDCLASS(AnimationNodeBlendSpace2DEditorPlugin, EditorPlugin);
+
+ AnimationNodeBlendSpace2DEditor *anim_tree_editor;
+ EditorNode *editor;
+ Button *button;
+
+public:
+ virtual String get_name() const { return "BlendSpace2D"; }
+ bool has_main_screen() const { return false; }
+ virtual void edit(Object *p_object);
+ virtual bool handles(Object *p_object) const;
+ virtual void make_visible(bool p_visible);
+
+ AnimationNodeBlendSpace2DEditorPlugin(EditorNode *p_node);
+ ~AnimationNodeBlendSpace2DEditorPlugin();
+};
+#endif // ANIMATION_BLEND_SPACE_2D_EDITOR_H
diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp
new file mode 100644
index 0000000000..3efb2736b5
--- /dev/null
+++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp
@@ -0,0 +1,848 @@
+#include "animation_blend_tree_editor_plugin.h"
+
+#include "core/io/resource_loader.h"
+#include "core/project_settings.h"
+#include "os/input.h"
+#include "os/keyboard.h"
+#include "scene/animation/animation_player.h"
+#include "scene/gui/menu_button.h"
+#include "scene/gui/panel.h"
+#include "scene/main/viewport.h"
+
+void AnimationNodeBlendTreeEditor::edit(AnimationNodeBlendTree *p_blend_tree) {
+
+ if (blend_tree.is_valid()) {
+ blend_tree->disconnect("removed_from_graph", this, "_removed_from_graph");
+ }
+
+ if (p_blend_tree) {
+ blend_tree = Ref<AnimationNodeBlendTree>(p_blend_tree);
+ } else {
+ blend_tree.unref();
+ }
+
+ if (blend_tree.is_null()) {
+ hide();
+ } else {
+ blend_tree->connect("removed_from_graph", this, "_removed_from_graph");
+
+ _update_graph();
+ }
+}
+
+void AnimationNodeBlendTreeEditor::add_custom_type(const String &p_name, const Ref<Script> &p_script) {
+
+ for (int i = 0; i < add_options.size(); i++) {
+ ERR_FAIL_COND(add_options[i].script == p_script);
+ }
+
+ AddOption ao;
+ ao.name = p_name;
+ ao.script = p_script;
+ add_options.push_back(ao);
+
+ _update_options_menu();
+}
+
+void AnimationNodeBlendTreeEditor::remove_custom_type(const Ref<Script> &p_script) {
+
+ for (int i = 0; i < add_options.size(); i++) {
+ if (add_options[i].script == p_script) {
+ add_options.remove(i);
+ return;
+ }
+ }
+
+ _update_options_menu();
+}
+
+void AnimationNodeBlendTreeEditor::_update_options_menu() {
+
+ add_node->get_popup()->clear();
+ for (int i = 0; i < add_options.size(); i++) {
+ add_node->get_popup()->add_item(add_options[i].name);
+ }
+}
+
+Size2 AnimationNodeBlendTreeEditor::get_minimum_size() const {
+
+ return Size2(10, 200);
+}
+
+void AnimationNodeBlendTreeEditor::_update_graph() {
+
+ if (updating)
+ return;
+
+ graph->set_scroll_ofs(blend_tree->get_graph_offset() * EDSCALE);
+
+ if (blend_tree->get_parent().is_valid()) {
+ goto_parent->show();
+ } else {
+ goto_parent->hide();
+ }
+ graph->clear_connections();
+ //erase all nodes
+ for (int i = 0; i < graph->get_child_count(); i++) {
+
+ if (Object::cast_to<GraphNode>(graph->get_child(i))) {
+ memdelete(graph->get_child(i));
+ i--;
+ }
+ }
+
+ animations.clear();
+
+ List<StringName> nodes;
+ blend_tree->get_node_list(&nodes);
+
+ for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) {
+
+ GraphNode *node = memnew(GraphNode);
+ graph->add_child(node);
+
+ Ref<AnimationNode> agnode = blend_tree->get_node(E->get());
+
+ if (!agnode->is_connected("changed", this, "_node_changed")) {
+ agnode->connect("changed", this, "_node_changed", varray(agnode->get_instance_id()), CONNECT_DEFERRED);
+ }
+
+ node->set_offset(agnode->get_position() * EDSCALE);
+
+ node->set_title(agnode->get_caption());
+ node->set_name(E->get());
+
+ int base = 0;
+ if (String(E->get()) != "output") {
+ LineEdit *name = memnew(LineEdit);
+ name->set_text(E->get());
+ name->set_expand_to_text_length(true);
+ node->add_child(name);
+ node->set_slot(0, false, 0, Color(), true, 0, get_color("font_color", "Label"));
+ name->connect("text_entered", this, "_node_renamed", varray(agnode));
+ name->connect("focus_exited", this, "_node_renamed_focus_out", varray(name, agnode));
+ base = 1;
+ node->set_show_close_button(true);
+ node->connect("close_request", this, "_delete_request", varray(E->get()), CONNECT_DEFERRED);
+ }
+
+ for (int i = 0; i < agnode->get_input_count(); i++) {
+ Label *in_name = memnew(Label);
+ node->add_child(in_name);
+ in_name->set_text(agnode->get_input_name(i));
+ node->set_slot(base + i, true, 0, get_color("font_color", "Label"), false, 0, Color());
+ }
+
+ node->connect("dragged", this, "_node_dragged", varray(agnode));
+
+ if (EditorNode::get_singleton()->item_has_editor(agnode.ptr())) {
+ node->add_child(memnew(HSeparator));
+ Button *open_in_editor = memnew(Button);
+ open_in_editor->set_text(TTR("Open Editor"));
+ open_in_editor->set_icon(get_icon("Edit", "EditorIcons"));
+ node->add_child(open_in_editor);
+ open_in_editor->connect("pressed", this, "_open_in_editor", varray(E->get()), CONNECT_DEFERRED);
+ open_in_editor->set_h_size_flags(SIZE_SHRINK_CENTER);
+ }
+
+ if (agnode->has_filter()) {
+
+ node->add_child(memnew(HSeparator));
+ Button *edit_filters = memnew(Button);
+ edit_filters->set_text(TTR("Edit Filters"));
+ edit_filters->set_icon(get_icon("AnimationFilter", "EditorIcons"));
+ node->add_child(edit_filters);
+ edit_filters->connect("pressed", this, "_edit_filters", varray(E->get()), CONNECT_DEFERRED);
+ edit_filters->set_h_size_flags(SIZE_SHRINK_CENTER);
+ }
+
+ Ref<AnimationNodeAnimation> anim = agnode;
+ if (anim.is_valid()) {
+
+ MenuButton *mb = memnew(MenuButton);
+ mb->set_text(anim->get_animation());
+ mb->set_icon(get_icon("Animation", "EditorIcons"));
+ Array options;
+
+ node->add_child(memnew(HSeparator));
+ node->add_child(mb);
+
+ ProgressBar *pb = memnew(ProgressBar);
+
+ AnimationTree *player = anim->get_tree();
+ if (player->has_node(player->get_animation_player())) {
+ AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(player->get_node(player->get_animation_player()));
+ if (ap) {
+ List<StringName> anims;
+ ap->get_animation_list(&anims);
+
+ for (List<StringName>::Element *F = anims.front(); F; F = F->next()) {
+ mb->get_popup()->add_item(F->get());
+ options.push_back(F->get());
+ }
+
+ if (ap->has_animation(anim->get_animation())) {
+ pb->set_max(ap->get_animation(anim->get_animation())->get_length());
+ }
+ }
+ }
+
+ pb->set_percent_visible(false);
+ animations[E->get()] = pb;
+ node->add_child(pb);
+
+ mb->get_popup()->connect("index_pressed", this, "_anim_selected", varray(options, E->get()), CONNECT_DEFERRED);
+ }
+
+ Ref<AnimationNodeOneShot> oneshot = agnode;
+ if (oneshot.is_valid()) {
+
+ HBoxContainer *play_stop = memnew(HBoxContainer);
+ play_stop->add_spacer();
+ Button *play = memnew(Button);
+ play->set_icon(get_icon("Play", "EditorIcons"));
+ play->connect("pressed", this, "_oneshot_start", varray(E->get()), CONNECT_DEFERRED);
+ play_stop->add_child(play);
+ Button *stop = memnew(Button);
+ stop->set_icon(get_icon("Stop", "EditorIcons"));
+ stop->connect("pressed", this, "_oneshot_stop", varray(E->get()), CONNECT_DEFERRED);
+ play_stop->add_child(stop);
+ play_stop->add_spacer();
+ node->add_child(play_stop);
+ }
+ }
+
+ List<AnimationNodeBlendTree::NodeConnection> connections;
+ blend_tree->get_node_connections(&connections);
+
+ for (List<AnimationNodeBlendTree::NodeConnection>::Element *E = connections.front(); E; E = E->next()) {
+
+ StringName from = E->get().output_node;
+ StringName to = E->get().input_node;
+ int to_idx = E->get().input_index;
+
+ graph->connect_node(from, 0, to, to_idx);
+ }
+}
+
+void AnimationNodeBlendTreeEditor::_add_node(int p_idx) {
+
+ ERR_FAIL_INDEX(p_idx, add_options.size());
+
+ Ref<AnimationNode> anode;
+
+ if (add_options[p_idx].type != String()) {
+ AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instance(add_options[p_idx].type));
+ ERR_FAIL_COND(!an);
+ anode = Ref<AnimationNode>(an);
+ } else {
+ ERR_FAIL_COND(add_options[p_idx].script.is_null());
+ String base_type = add_options[p_idx].script->get_instance_base_type();
+ AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instance(base_type));
+ ERR_FAIL_COND(!an);
+ anode = Ref<AnimationNode>(an);
+ anode->set_script(add_options[p_idx].script.get_ref_ptr());
+ }
+
+ Point2 instance_pos = graph->get_scroll_ofs() + graph->get_size() * 0.5;
+
+ anode->set_position(instance_pos);
+
+ String base_name = add_options[p_idx].name;
+ int base = 1;
+ String name = base_name;
+ while (blend_tree->has_node(name)) {
+ base++;
+ name = base_name + " " + itos(base);
+ }
+
+ undo_redo->create_action("Add Node to BlendTree");
+ undo_redo->add_do_method(blend_tree.ptr(), "add_node", name, anode);
+ undo_redo->add_undo_method(blend_tree.ptr(), "remove_node", name);
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+}
+
+void AnimationNodeBlendTreeEditor::_node_dragged(const Vector2 &p_from, const Vector2 &p_to, Ref<AnimationNode> p_node) {
+
+ updating = true;
+ undo_redo->create_action("Node Moved");
+ undo_redo->add_do_method(p_node.ptr(), "set_position", p_to / EDSCALE);
+ undo_redo->add_undo_method(p_node.ptr(), "set_position", p_from / EDSCALE);
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+}
+
+void AnimationNodeBlendTreeEditor::_connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index) {
+
+ AnimationNodeBlendTree::ConnectionError err = blend_tree->can_connect_node(p_to, p_to_index, p_from);
+
+ if (err != AnimationNodeBlendTree::CONNECTION_OK) {
+ EditorNode::get_singleton()->show_warning(TTR("Unable to connect, port may be in use or connection may be invalid."));
+ return;
+ }
+
+ undo_redo->create_action("Nodes Connected");
+ undo_redo->add_do_method(blend_tree.ptr(), "connect_node", p_to, p_to_index, p_from);
+ undo_redo->add_undo_method(blend_tree.ptr(), "disconnect_node", p_to, p_to_index, p_from);
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+}
+
+void AnimationNodeBlendTreeEditor::_disconnection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index) {
+
+ graph->disconnect_node(p_from, p_from_index, p_to, p_to_index);
+
+ updating = true;
+ undo_redo->create_action("Nodes Disconnected");
+ undo_redo->add_do_method(blend_tree.ptr(), "disconnect_node", p_to, p_to_index);
+ undo_redo->add_undo_method(blend_tree.ptr(), "connect_node", p_to, p_to_index, p_from);
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+}
+
+void AnimationNodeBlendTreeEditor::_anim_selected(int p_index, Array p_options, const String &p_node) {
+
+ String option = p_options[p_index];
+
+ Ref<AnimationNodeAnimation> anim = blend_tree->get_node(p_node);
+ ERR_FAIL_COND(!anim.is_valid());
+
+ undo_redo->create_action("Set Animation");
+ undo_redo->add_do_method(anim.ptr(), "set_animation", option);
+ undo_redo->add_undo_method(anim.ptr(), "set_animation", anim->get_animation());
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+}
+
+void AnimationNodeBlendTreeEditor::_delete_request(const String &p_which) {
+
+ undo_redo->create_action("Delete Node");
+ undo_redo->add_do_method(blend_tree.ptr(), "remove_node", p_which);
+ undo_redo->add_undo_method(blend_tree.ptr(), "add_node", p_which, blend_tree->get_node(p_which));
+
+ List<AnimationNodeBlendTree::NodeConnection> conns;
+ blend_tree->get_node_connections(&conns);
+
+ for (List<AnimationNodeBlendTree::NodeConnection>::Element *E = conns.front(); E; E = E->next()) {
+ if (E->get().output_node == p_which || E->get().input_node == p_which) {
+ undo_redo->add_undo_method(blend_tree.ptr(), "connect_node", E->get().input_node, E->get().input_index, E->get().output_node);
+ }
+ }
+
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+}
+
+void AnimationNodeBlendTreeEditor::_oneshot_start(const StringName &p_name) {
+
+ Ref<AnimationNodeOneShot> os = blend_tree->get_node(p_name);
+ ERR_FAIL_COND(!os.is_valid());
+ os->start();
+}
+
+void AnimationNodeBlendTreeEditor::_oneshot_stop(const StringName &p_name) {
+
+ Ref<AnimationNodeOneShot> os = blend_tree->get_node(p_name);
+ ERR_FAIL_COND(!os.is_valid());
+ os->stop();
+}
+
+void AnimationNodeBlendTreeEditor::_node_selected(Object *p_node) {
+
+ GraphNode *gn = Object::cast_to<GraphNode>(p_node);
+ ERR_FAIL_COND(!gn);
+
+ String name = gn->get_name();
+
+ Ref<AnimationNode> anode = blend_tree->get_node(name);
+ ERR_FAIL_COND(!anode.is_valid());
+
+ EditorNode::get_singleton()->push_item(anode.ptr(), "", true);
+}
+
+void AnimationNodeBlendTreeEditor::_open_in_editor(const String &p_which) {
+
+ Ref<AnimationNode> an = blend_tree->get_node(p_which);
+ ERR_FAIL_COND(!an.is_valid())
+ EditorNode::get_singleton()->edit_item(an.ptr());
+}
+
+void AnimationNodeBlendTreeEditor::_open_parent() {
+ if (blend_tree->get_parent().is_valid()) {
+ EditorNode::get_singleton()->edit_item(blend_tree->get_parent().ptr());
+ }
+}
+
+void AnimationNodeBlendTreeEditor::_filter_toggled() {
+
+ updating = true;
+ undo_redo->create_action("Toggle filter on/off");
+ undo_redo->add_do_method(_filter_edit.ptr(), "set_filter_enabled", filter_enabled->is_pressed());
+ undo_redo->add_undo_method(_filter_edit.ptr(), "set_filter_enabled", _filter_edit->is_filter_enabled());
+ undo_redo->add_do_method(this, "_update_filters", _filter_edit);
+ undo_redo->add_undo_method(this, "_update_filters", _filter_edit);
+ undo_redo->commit_action();
+ updating = false;
+}
+
+void AnimationNodeBlendTreeEditor::_filter_edited() {
+
+ TreeItem *edited = filters->get_edited();
+ ERR_FAIL_COND(!edited);
+
+ NodePath edited_path = edited->get_metadata(0);
+ bool filtered = edited->is_checked(0);
+
+ updating = true;
+ undo_redo->create_action("Change filter");
+ undo_redo->add_do_method(_filter_edit.ptr(), "set_filter_path", edited_path, filtered);
+ undo_redo->add_undo_method(_filter_edit.ptr(), "set_filter_path", edited_path, _filter_edit->is_path_filtered(edited_path));
+ undo_redo->add_do_method(this, "_update_filters", _filter_edit);
+ undo_redo->add_undo_method(this, "_update_filters", _filter_edit);
+ undo_redo->commit_action();
+ updating = false;
+}
+
+bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &anode) {
+
+ if (updating || _filter_edit != anode)
+ return false;
+
+ NodePath player_path = anode->get_tree()->get_animation_player();
+
+ if (!anode->get_tree()->has_node(player_path)) {
+ EditorNode::get_singleton()->show_warning(TTR("No animation player set, so unable to retrieve track names."));
+ return false;
+ }
+
+ AnimationPlayer *player = Object::cast_to<AnimationPlayer>(anode->get_tree()->get_node(player_path));
+ if (!player) {
+ EditorNode::get_singleton()->show_warning(TTR("Player path set is invalid, so unable to retrieve track names."));
+ return false;
+ }
+
+ Node *base = player->get_node(player->get_root());
+
+ if (!base) {
+ EditorNode::get_singleton()->show_warning(TTR("Animation player has no valid root node path, so unable to retrieve track names."));
+ return false;
+ }
+
+ updating = true;
+
+ Set<String> paths;
+ {
+ List<StringName> animations;
+ player->get_animation_list(&animations);
+
+ for (List<StringName>::Element *E = animations.front(); E; E = E->next()) {
+
+ Ref<Animation> anim = player->get_animation(E->get());
+ for (int i = 0; i < anim->get_track_count(); i++) {
+ paths.insert(anim->track_get_path(i));
+ }
+ }
+ }
+
+ filter_enabled->set_pressed(anode->is_filter_enabled());
+ filters->clear();
+ TreeItem *root = filters->create_item();
+
+ Map<String, TreeItem *> parenthood;
+
+ for (Set<String>::Element *E = paths.front(); E; E = E->next()) {
+
+ NodePath path = E->get();
+ TreeItem *ti = NULL;
+ String accum;
+ for (int i = 0; i < path.get_name_count(); i++) {
+ String name = path.get_name(i);
+ if (accum != String()) {
+ accum += "/";
+ }
+ accum += name;
+ if (!parenthood.has(accum)) {
+ if (ti) {
+ ti = filters->create_item(ti);
+ } else {
+ ti = filters->create_item(root);
+ }
+ parenthood[accum] = ti;
+ ti->set_text(0, name);
+ ti->set_selectable(0, false);
+ ti->set_editable(0, false);
+
+ if (base->has_node(accum)) {
+ Node *node = base->get_node(accum);
+ if (has_icon(node->get_class(), "EditorIcons")) {
+ ti->set_icon(0, get_icon(node->get_class(), "EditorIcons"));
+ } else {
+ ti->set_icon(0, get_icon("Node", "EditorIcons"));
+ }
+ }
+
+ } else {
+ ti = parenthood[accum];
+ }
+ }
+
+ Node *node = NULL;
+ if (base->has_node(accum)) {
+ node = base->get_node(accum);
+ }
+ if (!node)
+ continue; //no node, cant edit
+
+ if (path.get_subname_count()) {
+
+ String concat = path.get_concatenated_subnames();
+
+ Skeleton *skeleton = Object::cast_to<Skeleton>(node);
+ if (skeleton && skeleton->find_bone(concat) != -1) {
+ //path in skeleton
+ String bone = concat;
+ int idx = skeleton->find_bone(bone);
+ List<String> bone_path;
+ while (idx != -1) {
+ bone_path.push_front(skeleton->get_bone_name(idx));
+ idx = skeleton->get_bone_parent(idx);
+ }
+
+ accum += ":";
+ for (List<String>::Element *F = bone_path.front(); F; F = F->next()) {
+ if (F != bone_path.front()) {
+ accum += "/";
+ }
+
+ accum += F->get();
+ if (!parenthood.has(accum)) {
+ ti = filters->create_item(ti);
+ parenthood[accum] = ti;
+ ti->set_text(0, F->get());
+ ti->set_selectable(0, false);
+ ti->set_editable(0, false);
+ ti->set_icon(0, get_icon("BoneAttachment", "EditorIcons"));
+ } else {
+ ti = parenthood[accum];
+ }
+ }
+
+ ti->set_editable(0, true);
+ ti->set_selectable(0, true);
+ ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ ti->set_text(0, concat);
+ ti->set_checked(0, anode->is_path_filtered(path));
+ ti->set_icon(0, get_icon("BoneAttachment", "EditorIcons"));
+ ti->set_metadata(0, path);
+
+ } else {
+ //just a property
+ ti = filters->create_item(ti);
+ ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ ti->set_text(0, concat);
+ ti->set_editable(0, true);
+ ti->set_selectable(0, true);
+ ti->set_checked(0, anode->is_path_filtered(path));
+ ti->set_metadata(0, path);
+ }
+ } else {
+ if (ti) {
+ //just a node, likely call or animation track
+ ti->set_editable(0, true);
+ ti->set_selectable(0, true);
+ ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ ti->set_checked(0, anode->is_path_filtered(path));
+ ti->set_metadata(0, path);
+ }
+ }
+ }
+
+ updating = false;
+
+ return true;
+}
+
+void AnimationNodeBlendTreeEditor::_edit_filters(const String &p_which) {
+
+ Ref<AnimationNode> anode = blend_tree->get_node(p_which);
+ ERR_FAIL_COND(!anode.is_valid());
+
+ _filter_edit = anode;
+ if (!_update_filters(anode))
+ return;
+
+ filter_dialog->popup_centered_minsize(Size2(500, 500) * EDSCALE);
+}
+
+void AnimationNodeBlendTreeEditor::_removed_from_graph() {
+ if (is_visible()) {
+ EditorNode::get_singleton()->edit_item(NULL);
+ }
+}
+
+void AnimationNodeBlendTreeEditor::_notification(int p_what) {
+
+ if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
+
+ goto_parent->set_icon(get_icon("MoveUp", "EditorIcons"));
+
+ error_panel->add_style_override("panel", get_stylebox("bg", "Tree"));
+ error_label->add_color_override("font_color", get_color("error_color", "Editor"));
+ }
+
+ if (p_what == NOTIFICATION_PROCESS) {
+
+ String error;
+
+ if (!blend_tree->get_tree()) {
+ error = TTR("BlendTree does not belong to an AnimationTree node.");
+ } else if (!blend_tree->get_tree()->is_active()) {
+ error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails.");
+ } else if (blend_tree->get_tree()->is_state_invalid()) {
+ error = blend_tree->get_tree()->get_invalid_state_reason();
+ }
+
+ if (error != error_label->get_text()) {
+ error_label->set_text(error);
+ if (error != String()) {
+ error_panel->show();
+ } else {
+ error_panel->hide();
+ }
+ }
+
+ List<AnimationNodeBlendTree::NodeConnection> conns;
+ blend_tree->get_node_connections(&conns);
+ for (List<AnimationNodeBlendTree::NodeConnection>::Element *E = conns.front(); E; E = E->next()) {
+ float activity = 0;
+ if (blend_tree->get_tree() && !blend_tree->get_tree()->is_state_invalid()) {
+ activity = blend_tree->get_connection_activity(E->get().input_node, E->get().input_index);
+ }
+ graph->set_connection_activity(E->get().output_node, 0, E->get().input_node, E->get().input_index, activity);
+ }
+
+ AnimationTree *graph_player = blend_tree->get_tree();
+ AnimationPlayer *player = NULL;
+ if (graph_player->has_node(graph_player->get_animation_player())) {
+ player = Object::cast_to<AnimationPlayer>(graph_player->get_node(graph_player->get_animation_player()));
+ }
+
+ if (player) {
+ for (Map<StringName, ProgressBar *>::Element *E = animations.front(); E; E = E->next()) {
+ Ref<AnimationNodeAnimation> an = blend_tree->get_node(E->key());
+ if (an.is_valid()) {
+ if (player->has_animation(an->get_animation())) {
+ Ref<Animation> anim = player->get_animation(an->get_animation());
+ if (anim.is_valid()) {
+ E->get()->set_max(anim->get_length());
+ E->get()->set_value(an->get_playback_time());
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void AnimationNodeBlendTreeEditor::_scroll_changed(const Vector2 &p_scroll) {
+ if (updating)
+ return;
+ updating = true;
+ blend_tree->set_graph_offset(p_scroll / EDSCALE);
+ updating = false;
+}
+
+void AnimationNodeBlendTreeEditor::_node_changed(ObjectID p_node) {
+
+ AnimationNode *an = Object::cast_to<AnimationNode>(ObjectDB::get_instance(p_node));
+ if (an && an->get_parent() == blend_tree) {
+ _update_graph();
+ }
+}
+
+void AnimationNodeBlendTreeEditor::_bind_methods() {
+
+ ClassDB::bind_method("_update_graph", &AnimationNodeBlendTreeEditor::_update_graph);
+ ClassDB::bind_method("_add_node", &AnimationNodeBlendTreeEditor::_add_node);
+ ClassDB::bind_method("_node_dragged", &AnimationNodeBlendTreeEditor::_node_dragged);
+ ClassDB::bind_method("_node_renamed", &AnimationNodeBlendTreeEditor::_node_renamed);
+ ClassDB::bind_method("_node_renamed_focus_out", &AnimationNodeBlendTreeEditor::_node_renamed_focus_out);
+ ClassDB::bind_method("_connection_request", &AnimationNodeBlendTreeEditor::_connection_request);
+ ClassDB::bind_method("_disconnection_request", &AnimationNodeBlendTreeEditor::_disconnection_request);
+ ClassDB::bind_method("_node_selected", &AnimationNodeBlendTreeEditor::_node_selected);
+ ClassDB::bind_method("_open_in_editor", &AnimationNodeBlendTreeEditor::_open_in_editor);
+ ClassDB::bind_method("_open_parent", &AnimationNodeBlendTreeEditor::_open_parent);
+ ClassDB::bind_method("_scroll_changed", &AnimationNodeBlendTreeEditor::_scroll_changed);
+ ClassDB::bind_method("_delete_request", &AnimationNodeBlendTreeEditor::_delete_request);
+ ClassDB::bind_method("_edit_filters", &AnimationNodeBlendTreeEditor::_edit_filters);
+ ClassDB::bind_method("_update_filters", &AnimationNodeBlendTreeEditor::_update_filters);
+ ClassDB::bind_method("_filter_edited", &AnimationNodeBlendTreeEditor::_filter_edited);
+ ClassDB::bind_method("_filter_toggled", &AnimationNodeBlendTreeEditor::_filter_toggled);
+ ClassDB::bind_method("_oneshot_start", &AnimationNodeBlendTreeEditor::_oneshot_start);
+ ClassDB::bind_method("_oneshot_stop", &AnimationNodeBlendTreeEditor::_oneshot_stop);
+ ClassDB::bind_method("_node_changed", &AnimationNodeBlendTreeEditor::_node_changed);
+ ClassDB::bind_method("_removed_from_graph", &AnimationNodeBlendTreeEditor::_removed_from_graph);
+
+ ClassDB::bind_method("_anim_selected", &AnimationNodeBlendTreeEditor::_anim_selected);
+}
+
+AnimationNodeBlendTreeEditor *AnimationNodeBlendTreeEditor::singleton = NULL;
+
+void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Ref<AnimationNode> p_node) {
+
+ String prev_name = blend_tree->get_node_name(p_node);
+ ERR_FAIL_COND(prev_name == String());
+ GraphNode *gn = Object::cast_to<GraphNode>(graph->get_node(prev_name));
+ ERR_FAIL_COND(!gn);
+
+ String new_name = p_text;
+
+ ERR_FAIL_COND(new_name == "" || new_name.find(".") != -1 || new_name.find("/") != -1)
+
+ ERR_FAIL_COND(new_name == prev_name);
+
+ String base_name = new_name;
+ int base = 1;
+ String name = base_name;
+ while (blend_tree->has_node(name)) {
+ base++;
+ name = base_name + " " + itos(base);
+ }
+
+ updating = true;
+ undo_redo->create_action("Node Renamed");
+ undo_redo->add_do_method(blend_tree.ptr(), "rename_node", prev_name, name);
+ undo_redo->add_undo_method(blend_tree.ptr(), "rename_node", name, prev_name);
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+ gn->set_name(new_name);
+ gn->set_size(gn->get_minimum_size());
+}
+
+void AnimationNodeBlendTreeEditor::_node_renamed_focus_out(Node *le, Ref<AnimationNode> p_node) {
+ _node_renamed(le->call("get_text"), p_node);
+}
+
+AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() {
+
+ singleton = this;
+ updating = false;
+
+ graph = memnew(GraphEdit);
+ add_child(graph);
+ graph->add_valid_right_disconnect_type(0);
+ graph->add_valid_left_disconnect_type(0);
+ graph->set_v_size_flags(SIZE_EXPAND_FILL);
+ graph->connect("connection_request", this, "_connection_request", varray(), CONNECT_DEFERRED);
+ graph->connect("disconnection_request", this, "_disconnection_request", varray(), CONNECT_DEFERRED);
+ graph->connect("node_selected", this, "_node_selected");
+ graph->connect("scroll_offset_changed", this, "_scroll_changed");
+
+ VSeparator *vs = memnew(VSeparator);
+ graph->get_zoom_hbox()->add_child(vs);
+ graph->get_zoom_hbox()->move_child(vs, 0);
+
+ add_node = memnew(MenuButton);
+ graph->get_zoom_hbox()->add_child(add_node);
+ add_node->set_text(TTR("Add Node.."));
+ graph->get_zoom_hbox()->move_child(add_node, 0);
+ add_node->get_popup()->connect("index_pressed", this, "_add_node");
+
+ goto_parent = memnew(Button);
+ graph->get_zoom_hbox()->add_child(goto_parent);
+ graph->get_zoom_hbox()->move_child(goto_parent, 0);
+ goto_parent->hide();
+ goto_parent->connect("pressed", this, "_open_parent");
+
+ add_options.push_back(AddOption("Animation", "AnimationNodeAnimation"));
+ add_options.push_back(AddOption("OneShot", "AnimationNodeOneShot"));
+ add_options.push_back(AddOption("Add2", "AnimationNodeAdd2"));
+ add_options.push_back(AddOption("Add3", "AnimationNodeAdd3"));
+ add_options.push_back(AddOption("Blend2", "AnimationNodeBlend2"));
+ add_options.push_back(AddOption("Blend3", "AnimationNodeBlend3"));
+ add_options.push_back(AddOption("Seek", "AnimationNodeTimeSeek"));
+ add_options.push_back(AddOption("TimeScale", "AnimationNodeTimeScale"));
+ add_options.push_back(AddOption("Transition", "AnimationNodeTransition"));
+ add_options.push_back(AddOption("BlendTree", "AnimationNodeBlendTree"));
+ add_options.push_back(AddOption("BlendSpace1D", "AnimationNodeBlendSpace1D"));
+ add_options.push_back(AddOption("BlendSpace2D", "AnimationNodeBlendSpace2D"));
+ add_options.push_back(AddOption("StateMachine", "AnimationNodeStateMachine"));
+ _update_options_menu();
+
+ error_panel = memnew(PanelContainer);
+ add_child(error_panel);
+ error_label = memnew(Label);
+ error_panel->add_child(error_label);
+ error_label->set_text("eh");
+
+ filter_dialog = memnew(AcceptDialog);
+ add_child(filter_dialog);
+ filter_dialog->set_title(TTR("Edit Filtered Tracks:"));
+
+ VBoxContainer *filter_vbox = memnew(VBoxContainer);
+ filter_dialog->add_child(filter_vbox);
+
+ filter_enabled = memnew(CheckBox);
+ filter_enabled->set_text(TTR("Enable filtering"));
+ filter_enabled->connect("pressed", this, "_filter_toggled");
+ filter_vbox->add_child(filter_enabled);
+
+ filters = memnew(Tree);
+ filter_vbox->add_child(filters);
+ filters->set_v_size_flags(SIZE_EXPAND_FILL);
+ filters->set_hide_root(true);
+ filters->connect("item_edited", this, "_filter_edited");
+
+ undo_redo = EditorNode::get_singleton()->get_undo_redo();
+}
+
+void AnimationNodeBlendTreeEditorPlugin::edit(Object *p_object) {
+
+ anim_tree_editor->edit(Object::cast_to<AnimationNodeBlendTree>(p_object));
+}
+
+bool AnimationNodeBlendTreeEditorPlugin::handles(Object *p_object) const {
+
+ return p_object->is_class("AnimationNodeBlendTree");
+}
+
+void AnimationNodeBlendTreeEditorPlugin::make_visible(bool p_visible) {
+
+ if (p_visible) {
+ //editor->hide_animation_player_editors();
+ //editor->animation_panel_make_visible(true);
+ button->show();
+ editor->make_bottom_panel_item_visible(anim_tree_editor);
+ anim_tree_editor->set_process(true);
+ } else {
+
+ if (anim_tree_editor->is_visible_in_tree())
+ editor->hide_bottom_panel();
+ button->hide();
+ anim_tree_editor->set_process(false);
+ }
+}
+
+AnimationNodeBlendTreeEditorPlugin::AnimationNodeBlendTreeEditorPlugin(EditorNode *p_node) {
+
+ editor = p_node;
+ anim_tree_editor = memnew(AnimationNodeBlendTreeEditor);
+ anim_tree_editor->set_custom_minimum_size(Size2(0, 300));
+
+ button = editor->add_bottom_panel_item(TTR("BlendTree"), anim_tree_editor);
+ button->hide();
+}
+
+AnimationNodeBlendTreeEditorPlugin::~AnimationNodeBlendTreeEditorPlugin() {
+}
diff --git a/editor/plugins/animation_blend_tree_editor_plugin.h b/editor/plugins/animation_blend_tree_editor_plugin.h
new file mode 100644
index 0000000000..deba3b2b0e
--- /dev/null
+++ b/editor/plugins/animation_blend_tree_editor_plugin.h
@@ -0,0 +1,117 @@
+#ifndef ANIMATION_BLEND_TREE_EDITOR_PLUGIN_H
+#define ANIMATION_BLEND_TREE_EDITOR_PLUGIN_H
+
+#include "editor/editor_node.h"
+#include "editor/editor_plugin.h"
+#include "editor/property_editor.h"
+#include "scene/animation/animation_blend_tree.h"
+#include "scene/gui/button.h"
+#include "scene/gui/graph_edit.h"
+#include "scene/gui/popup.h"
+#include "scene/gui/tree.h"
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+
+class AnimationNodeBlendTreeEditor : public VBoxContainer {
+
+ GDCLASS(AnimationNodeBlendTreeEditor, VBoxContainer);
+
+ Ref<AnimationNodeBlendTree> blend_tree;
+ GraphEdit *graph;
+ MenuButton *add_node;
+ Button *goto_parent;
+
+ PanelContainer *error_panel;
+ Label *error_label;
+
+ UndoRedo *undo_redo;
+
+ AcceptDialog *filter_dialog;
+ Tree *filters;
+ CheckBox *filter_enabled;
+
+ Map<StringName, ProgressBar *> animations;
+
+ void _update_graph();
+
+ struct AddOption {
+ String name;
+ String type;
+ Ref<Script> script;
+ AddOption(const String &p_name = String(), const String &p_type = String()) {
+ name = p_name;
+ type = p_type;
+ }
+ };
+
+ Vector<AddOption> add_options;
+
+ void _add_node(int p_idx);
+ void _update_options_menu();
+
+ static AnimationNodeBlendTreeEditor *singleton;
+
+ void _node_dragged(const Vector2 &p_from, const Vector2 &p_to, Ref<AnimationNode> p_node);
+ void _node_renamed(const String &p_text, Ref<AnimationNode> p_node);
+ void _node_renamed_focus_out(Node *le, Ref<AnimationNode> p_node);
+
+ bool updating;
+
+ void _connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index);
+ void _disconnection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index);
+
+ void _scroll_changed(const Vector2 &p_scroll);
+ void _node_selected(Object *p_node);
+ void _open_in_editor(const String &p_which);
+ void _open_parent();
+ void _anim_selected(int p_index, Array p_options, const String &p_node);
+ void _delete_request(const String &p_which);
+ void _oneshot_start(const StringName &p_name);
+ void _oneshot_stop(const StringName &p_name);
+
+ bool _update_filters(const Ref<AnimationNode> &anode);
+ void _edit_filters(const String &p_which);
+ void _filter_edited();
+ void _filter_toggled();
+ Ref<AnimationNode> _filter_edit;
+
+ void _node_changed(ObjectID p_node);
+
+ void _removed_from_graph();
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ static AnimationNodeBlendTreeEditor *get_singleton() { return singleton; }
+
+ void add_custom_type(const String &p_name, const Ref<Script> &p_script);
+ void remove_custom_type(const Ref<Script> &p_script);
+
+ virtual Size2 get_minimum_size() const;
+ void edit(AnimationNodeBlendTree *p_blend_tree);
+ AnimationNodeBlendTreeEditor();
+};
+
+class AnimationNodeBlendTreeEditorPlugin : public EditorPlugin {
+
+ GDCLASS(AnimationNodeBlendTreeEditorPlugin, EditorPlugin);
+
+ AnimationNodeBlendTreeEditor *anim_tree_editor;
+ EditorNode *editor;
+ Button *button;
+
+public:
+ virtual String get_name() const { return "BlendTree"; }
+ bool has_main_screen() const { return false; }
+ virtual void edit(Object *p_object);
+ virtual bool handles(Object *p_object) const;
+ virtual void make_visible(bool p_visible);
+
+ AnimationNodeBlendTreeEditorPlugin(EditorNode *p_node);
+ ~AnimationNodeBlendTreeEditorPlugin();
+};
+
+#endif // ANIMATION_BLEND_TREE_EDITOR_PLUGIN_H
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index 23c5e36a92..248e386bf1 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -30,7 +30,7 @@
#include "animation_player_editor_plugin.h"
-#include "editor/animation_editor.h"
+#include "editor/animation_track_editor.h"
#include "editor/editor_settings.h"
#include "io/resource_loader.h"
#include "io/resource_saver.h"
@@ -50,9 +50,9 @@ void AnimationPlayerEditor::_node_removed(Node *p_node) {
set_process(false);
- key_editor->set_animation(Ref<Animation>());
- key_editor->set_root(NULL);
- key_editor->show_select_node_warning(true);
+ track_editor->set_animation(Ref<Animation>());
+ track_editor->set_root(NULL);
+ track_editor->show_select_node_warning(true);
_update_player();
//editor->animation_editor_make_visible(false);
}
@@ -84,7 +84,7 @@ void AnimationPlayerEditor::_notification(int p_what) {
}
}
frame->set_value(player->get_current_animation_position());
- key_editor->set_anim_pos(player->get_current_animation_position());
+ track_editor->set_anim_pos(player->get_current_animation_position());
EditorNode::get_singleton()->get_inspector()->refresh();
} else if (last_active) {
@@ -101,8 +101,6 @@ void AnimationPlayerEditor::_notification(int p_what) {
case NOTIFICATION_ENTER_TREE: {
- save_anim->get_popup()->connect("id_pressed", this, "_animation_save_menu");
-
tool_anim->get_popup()->connect("id_pressed", this, "_animation_tool_menu");
onion_skinning->get_popup()->connect("id_pressed", this, "_onion_skinning_menu");
@@ -121,16 +119,8 @@ void AnimationPlayerEditor::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED: {
- add_anim->set_icon(get_icon("New", "EditorIcons"));
- rename_anim->set_icon(get_icon("Rename", "EditorIcons"));
- duplicate_anim->set_icon(get_icon("Duplicate", "EditorIcons"));
autoplay->set_icon(get_icon("AutoPlay", "EditorIcons"));
- load_anim->set_icon(get_icon("Folder", "EditorIcons"));
- save_anim->set_icon(get_icon("Save", "EditorIcons"));
-
- remove_anim->set_icon(get_icon("Remove", "EditorIcons"));
- blend_anim->set_icon(get_icon("Blend", "EditorIcons"));
play->set_icon(get_icon("PlayStart", "EditorIcons"));
play_from->set_icon(get_icon("Play", "EditorIcons"));
play_bw->set_icon(get_icon("PlayStartBackwards", "EditorIcons"));
@@ -138,11 +128,27 @@ void AnimationPlayerEditor::_notification(int p_what) {
autoplay_icon = get_icon("AutoPlay", "EditorIcons");
stop->set_icon(get_icon("Stop", "EditorIcons"));
- resource_edit_anim->set_icon(get_icon("EditResource", "EditorIcons"));
+
pin->set_icon(get_icon("Pin", "EditorIcons"));
- tool_anim->set_icon(get_icon("Tools", "EditorIcons"));
onion_skinning->set_icon(get_icon("Onion", "EditorIcons"));
+ tool_anim->add_style_override("normal", get_stylebox("normal", "Button"));
+ track_editor->get_edit_menu()->add_style_override("normal", get_stylebox("normal", "Button"));
+
+#define ITEM_ICON(m_item, m_icon) tool_anim->get_popup()->set_item_icon(tool_anim->get_popup()->get_item_index(m_item), get_icon(m_icon, "EditorIcons"))
+
+ ITEM_ICON(TOOL_NEW_ANIM, "New");
+ ITEM_ICON(TOOL_LOAD_ANIM, "Load");
+ ITEM_ICON(TOOL_SAVE_ANIM, "Save");
+ ITEM_ICON(TOOL_SAVE_AS_ANIM, "Save");
+ ITEM_ICON(TOOL_DUPLICATE_ANIM, "Duplicate");
+ ITEM_ICON(TOOL_RENAME_ANIM, "Rename");
+ ITEM_ICON(TOOL_EDIT_TRANSITIONS, "Blend");
+ ITEM_ICON(TOOL_EDIT_RESOURCE, "Edit");
+ ITEM_ICON(TOOL_REMOVE_ANIM, "Remove");
+ //ITEM_ICON(TOOL_COPY_ANIM, "Copy");
+ //ITEM_ICON(TOOL_PASTE_ANIM, "Paste");
+
} break;
}
}
@@ -304,10 +310,10 @@ void AnimationPlayerEditor::_animation_selected(int p_which) {
Ref<Animation> anim = player->get_animation(current);
{
- key_editor->set_animation(anim);
+ track_editor->set_animation(anim);
Node *root = player->get_node(player->get_root());
if (root) {
- key_editor->set_root(root);
+ track_editor->set_root(root);
}
}
frame->set_max(anim->get_length());
@@ -317,8 +323,8 @@ void AnimationPlayerEditor::_animation_selected(int p_which) {
frame->set_step(0.00001);
} else {
- key_editor->set_animation(Ref<Animation>());
- key_editor->set_root(NULL);
+ track_editor->set_animation(Ref<Animation>());
+ track_editor->set_root(NULL);
}
autoplay->set_pressed(current == player->get_autoplay());
@@ -704,16 +710,16 @@ void AnimationPlayerEditor::_animation_edit() {
if (animation->get_item_count()) {
String current = animation->get_item_text(animation->get_selected());
Ref<Animation> anim = player->get_animation(current);
- key_editor->set_animation(anim);
+ track_editor->set_animation(anim);
Node *root = player->get_node(player->get_root());
if (root) {
- key_editor->set_root(root);
+ track_editor->set_root(root);
}
} else {
- key_editor->set_animation(Ref<Animation>());
- key_editor->set_root(NULL);
+ track_editor->set_animation(Ref<Animation>());
+ track_editor->set_root(NULL);
}
}
void AnimationPlayerEditor::_dialog_action(String p_file) {
@@ -810,8 +816,16 @@ void AnimationPlayerEditor::_update_player() {
animation->clear();
- add_anim->set_disabled(player == NULL);
- load_anim->set_disabled(player == NULL);
+#define ITEM_DISABLED(m_item, m_disabled) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), m_disabled)
+
+ ITEM_DISABLED(TOOL_SAVE_ANIM, animlist.size() == 0);
+ ITEM_DISABLED(TOOL_SAVE_AS_ANIM, animlist.size() == 0);
+ ITEM_DISABLED(TOOL_DUPLICATE_ANIM, animlist.size() == 0);
+ ITEM_DISABLED(TOOL_RENAME_ANIM, animlist.size() == 0);
+ ITEM_DISABLED(TOOL_EDIT_TRANSITIONS, animlist.size() == 0);
+ ITEM_DISABLED(TOOL_COPY_ANIM, animlist.size() == 0);
+ ITEM_DISABLED(TOOL_REMOVE_ANIM, animlist.size() == 0);
+
stop->set_disabled(animlist.size() == 0);
play->set_disabled(animlist.size() == 0);
play_bw->set_disabled(animlist.size() == 0);
@@ -820,12 +834,6 @@ void AnimationPlayerEditor::_update_player() {
frame->set_editable(animlist.size() != 0);
animation->set_disabled(animlist.size() == 0);
autoplay->set_disabled(animlist.size() == 0);
- duplicate_anim->set_disabled(animlist.size() == 0);
- rename_anim->set_disabled(animlist.size() == 0);
- blend_anim->set_disabled(animlist.size() == 0);
- remove_anim->set_disabled(animlist.size() == 0);
- resource_edit_anim->set_disabled(animlist.size() == 0);
- save_anim->set_disabled(animlist.size() == 0);
tool_anim->set_disabled(player == NULL);
onion_skinning->set_disabled(player == NULL);
pin->set_disabled(player == NULL);
@@ -863,10 +871,10 @@ void AnimationPlayerEditor::_update_player() {
if (animation->get_item_count()) {
String current = animation->get_item_text(animation->get_selected());
Ref<Animation> anim = player->get_animation(current);
- key_editor->set_animation(anim);
+ track_editor->set_animation(anim);
Node *root = player->get_node(player->get_root());
if (root) {
- key_editor->set_root(root);
+ track_editor->set_root(root);
}
}
@@ -884,9 +892,9 @@ void AnimationPlayerEditor::edit(AnimationPlayer *p_player) {
if (player) {
_update_player();
- key_editor->show_select_node_warning(false);
+ track_editor->show_select_node_warning(false);
} else {
- key_editor->show_select_node_warning(true);
+ track_editor->show_select_node_warning(true);
//hide();
}
@@ -1024,7 +1032,7 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set) {
player->seek(pos, true);
}
- key_editor->set_anim_pos(pos);
+ track_editor->set_anim_pos(pos);
updating = true;
};
@@ -1084,16 +1092,55 @@ void AnimationPlayerEditor::_hide_anim_editors() {
hide();
set_process(false);
- key_editor->set_animation(Ref<Animation>());
- key_editor->set_root(NULL);
- key_editor->show_select_node_warning(true);
+ track_editor->set_animation(Ref<Animation>());
+ track_editor->set_root(NULL);
+ track_editor->show_select_node_warning(true);
//editor->animation_editor_make_visible(false);
}
+void AnimationPlayerEditor::_animation_about_to_show_menu() {
+}
+
void AnimationPlayerEditor::_animation_tool_menu(int p_option) {
+ String current = animation->get_item_text(animation->get_selected());
+ Ref<Animation> anim;
+ if (current != "") {
+ anim = player->get_animation(current);
+ }
+
switch (p_option) {
+ case TOOL_NEW_ANIM: {
+ _animation_new();
+ } break;
+
+ case TOOL_LOAD_ANIM: {
+ _animation_load();
+ break;
+ } break;
+ case TOOL_SAVE_ANIM: {
+ if (anim.is_valid()) {
+ _animation_save(anim);
+ }
+ } break;
+ case TOOL_SAVE_AS_ANIM: {
+ if (anim.is_valid()) {
+ _animation_save_as(anim);
+ }
+ } break;
+ case TOOL_DUPLICATE_ANIM: {
+ _animation_duplicate();
+ } break;
+ case TOOL_RENAME_ANIM: {
+ _animation_rename();
+ } break;
+ case TOOL_EDIT_TRANSITIONS: {
+ _animation_blend();
+ } break;
+ case TOOL_REMOVE_ANIM: {
+ _animation_remove();
+ } break;
case TOOL_COPY_ANIM: {
if (!animation->get_item_count()) {
@@ -1156,23 +1203,6 @@ void AnimationPlayerEditor::_animation_tool_menu(int p_option) {
}
}
-void AnimationPlayerEditor::_animation_save_menu(int p_option) {
-
- String current = animation->get_item_text(animation->get_selected());
- if (current != "") {
- Ref<Animation> anim = player->get_animation(current);
-
- switch (p_option) {
- case ANIM_SAVE:
- _animation_save(anim);
- break;
- case ANIM_SAVE_AS:
- _animation_save_as(anim);
- break;
- }
- }
-}
-
void AnimationPlayerEditor::_onion_skinning_menu(int p_option) {
PopupMenu *menu = onion_skinning->get_popup();
@@ -1431,7 +1461,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
float pos = cpos + step_off * anim->get_step();
- bool valid = anim->has_loop() || pos >= 0 && pos <= anim->get_length();
+ bool valid = anim->has_loop() || (pos >= 0 && pos <= anim->get_length());
onion.captures_valid[cidx] = valid;
if (valid) {
player->seek(pos, true);
@@ -1494,6 +1524,10 @@ void AnimationPlayerEditor::_stop_onion_skinning() {
}
}
+void AnimationPlayerEditor::_pin_pressed() {
+ EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor()->update_tree();
+}
+
void AnimationPlayerEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_gui_input"), &AnimationPlayerEditor::_gui_input);
@@ -1532,11 +1566,13 @@ void AnimationPlayerEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_blend_editor_next_changed"), &AnimationPlayerEditor::_blend_editor_next_changed);
ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &AnimationPlayerEditor::_unhandled_key_input);
ClassDB::bind_method(D_METHOD("_animation_tool_menu"), &AnimationPlayerEditor::_animation_tool_menu);
- ClassDB::bind_method(D_METHOD("_animation_save_menu"), &AnimationPlayerEditor::_animation_save_menu);
+
ClassDB::bind_method(D_METHOD("_onion_skinning_menu"), &AnimationPlayerEditor::_onion_skinning_menu);
ClassDB::bind_method(D_METHOD("_editor_visibility_changed"), &AnimationPlayerEditor::_editor_visibility_changed);
ClassDB::bind_method(D_METHOD("_prepare_onion_layers_1"), &AnimationPlayerEditor::_prepare_onion_layers_1);
ClassDB::bind_method(D_METHOD("_prepare_onion_layers_2"), &AnimationPlayerEditor::_prepare_onion_layers_2);
+
+ ClassDB::bind_method(D_METHOD("_pin_pressed"), &AnimationPlayerEditor::_pin_pressed);
}
AnimationPlayerEditor *AnimationPlayerEditor::singleton = NULL;
@@ -1606,26 +1642,6 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay
scale->set_tooltip(TTR("Scale animation playback globally for the node."));
scale->hide();
- add_anim = memnew(ToolButton);
- ED_SHORTCUT("animation_player_editor/add_animation", TTR("Create new animation in player."));
- add_anim->set_shortcut(ED_GET_SHORTCUT("animation_player_editor/add_animation"));
- add_anim->set_tooltip(TTR("Create new animation in player."));
-
- hb->add_child(add_anim);
-
- load_anim = memnew(ToolButton);
- ED_SHORTCUT("animation_player_editor/load_from_disk", TTR("Load animation from disk."));
- add_anim->set_shortcut(ED_GET_SHORTCUT("animation_player_editor/load_from_disk"));
- load_anim->set_tooltip(TTR("Load an animation from disk."));
- hb->add_child(load_anim);
-
- save_anim = memnew(MenuButton);
- save_anim->set_tooltip(TTR("Save the current animation."));
- save_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/save", TTR("Save")), ANIM_SAVE);
- save_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/save_as", TTR("Save As")), ANIM_SAVE_AS);
- save_anim->set_focus_mode(Control::FOCUS_NONE);
- hb->add_child(save_anim);
-
accept = memnew(AcceptDialog);
add_child(accept);
accept->connect("confirmed", this, "_menu_confirm_current");
@@ -1634,23 +1650,28 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay
add_child(delete_dialog);
delete_dialog->connect("confirmed", this, "_animation_remove_confirmed");
- duplicate_anim = memnew(ToolButton);
- hb->add_child(duplicate_anim);
- ED_SHORTCUT("animation_player_editor/duplicate_animation", TTR("Duplicate Animation"));
- duplicate_anim->set_shortcut(ED_GET_SHORTCUT("animation_player_editor/duplicate_animation"));
- duplicate_anim->set_tooltip(TTR("Duplicate Animation"));
-
- rename_anim = memnew(ToolButton);
- hb->add_child(rename_anim);
- ED_SHORTCUT("animation_player_editor/rename_animation", TTR("Rename Animation"));
- rename_anim->set_shortcut(ED_GET_SHORTCUT("animation_player_editor/rename_animation"));
- rename_anim->set_tooltip(TTR("Rename Animation"));
-
- remove_anim = memnew(ToolButton);
- hb->add_child(remove_anim);
- ED_SHORTCUT("animation_player_editor/remove_animation", TTR("Remove Animation"));
- remove_anim->set_shortcut(ED_GET_SHORTCUT("animation_player_editor/remove_animation"));
- remove_anim->set_tooltip(TTR("Remove Animation"));
+ tool_anim = memnew(MenuButton);
+ tool_anim->set_flat(false);
+ //tool_anim->set_flat(false);
+ tool_anim->set_tooltip(TTR("Animation Tools"));
+ tool_anim->set_text(TTR("Animation"));
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/new_animation", TTR("New")), TOOL_NEW_ANIM);
+ tool_anim->get_popup()->add_separator();
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/open_animation", TTR("Load")), TOOL_LOAD_ANIM);
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/save_animation", TTR("Save")), TOOL_SAVE_ANIM);
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/save_as_animation", TTR("Save As...")), TOOL_SAVE_AS_ANIM);
+ tool_anim->get_popup()->add_separator();
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/copy_animation", TTR("Copy")), TOOL_COPY_ANIM);
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/paste_animation", TTR("Paste")), TOOL_PASTE_ANIM);
+ tool_anim->get_popup()->add_separator();
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/duplicate_animation", TTR("Duplicate")), TOOL_DUPLICATE_ANIM);
+ tool_anim->get_popup()->add_separator();
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/rename_animation", TTR("Rename...")), TOOL_RENAME_ANIM);
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/edit_transitions", TTR("Edit Transitions...")), TOOL_EDIT_TRANSITIONS);
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/open_animation_in_inspector", TTR("Open in Inspector")), TOOL_EDIT_RESOURCE);
+ tool_anim->get_popup()->add_separator();
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/remove_animation", TTR("Remove")), TOOL_REMOVE_ANIM);
+ hb->add_child(tool_anim);
animation = memnew(OptionButton);
hb->add_child(animation);
@@ -1662,18 +1683,12 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay
hb->add_child(autoplay);
autoplay->set_tooltip(TTR("Autoplay on Load"));
- blend_anim = memnew(ToolButton);
- hb->add_child(blend_anim);
- blend_anim->set_tooltip(TTR("Edit Target Blend Times"));
-
- tool_anim = memnew(MenuButton);
- //tool_anim->set_flat(false);
- tool_anim->set_tooltip(TTR("Animation Tools"));
- tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/copy_animation", TTR("Copy Animation")), TOOL_COPY_ANIM);
- tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/paste_animation", TTR("Paste Animation")), TOOL_PASTE_ANIM);
//tool_anim->get_popup()->add_separator();
//tool_anim->get_popup()->add_item("Edit Anim Resource",TOOL_PASTE_ANIM);
- hb->add_child(tool_anim);
+
+ track_editor = memnew(AnimationTrackEditor);
+
+ hb->add_child(track_editor->get_edit_menu());
onion_skinning = memnew(MenuButton);
//onion_skinning->set_flat(false);
@@ -1702,10 +1717,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay
pin->set_toggle_mode(true);
pin->set_tooltip(TTR("Pin AnimationPlayer"));
hb->add_child(pin);
-
- resource_edit_anim = memnew(Button);
- hb->add_child(resource_edit_anim);
- resource_edit_anim->hide();
+ pin->connect("pressed", this, "_pin_pressed");
file = memnew(EditorFileDialog);
add_child(file);
@@ -1758,16 +1770,10 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay
play_bw_from->connect("pressed", this, "_play_bw_from_pressed");
stop->connect("pressed", this, "_stop_pressed");
//pause->connect("pressed", this,"_pause_pressed");
- add_anim->connect("pressed", this, "_animation_new");
- rename_anim->connect("pressed", this, "_animation_rename");
- load_anim->connect("pressed", this, "_animation_load");
- duplicate_anim->connect("pressed", this, "_animation_duplicate");
//frame->connect("text_entered", this,"_seek_frame_changed");
- blend_anim->connect("pressed", this, "_animation_blend");
- remove_anim->connect("pressed", this, "_animation_remove");
animation->connect("item_selected", this, "_animation_selected", Vector<Variant>(), true);
- resource_edit_anim->connect("pressed", this, "_animation_resource_edit");
+
file->connect("file_selected", this, "_dialog_action");
frame->connect("value_changed", this, "_seek_value_changed", Vector<Variant>(), true);
scale->connect("text_entered", this, "_scale_changed", Vector<Variant>(), true);
@@ -1777,18 +1783,17 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay
set_process_unhandled_key_input(true);
- key_editor = memnew(AnimationKeyEditor);
- add_child(key_editor);
- key_editor->set_v_size_flags(SIZE_EXPAND_FILL);
- key_editor->connect("timeline_changed", this, "_animation_key_editor_seek");
- key_editor->connect("animation_len_changed", this, "_animation_key_editor_anim_len_changed");
- key_editor->connect("animation_step_changed", this, "_animation_key_editor_anim_step_changed");
+ add_child(track_editor);
+ track_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+ track_editor->connect("timeline_changed", this, "_animation_key_editor_seek");
+ track_editor->connect("animation_len_changed", this, "_animation_key_editor_anim_len_changed");
+ track_editor->connect("animation_step_changed", this, "_animation_key_editor_anim_step_changed");
_update_player();
// Onion skinning
- key_editor->connect("visibility_changed", this, "_editor_visibility_changed");
+ track_editor->connect("visibility_changed", this, "_editor_visibility_changed");
onion.enabled = false;
onion.past = true;
diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h
index a7b7c6c465..5ac7b99903 100644
--- a/editor/plugins/animation_player_editor_plugin.h
+++ b/editor/plugins/animation_player_editor_plugin.h
@@ -42,8 +42,9 @@
/**
@author Juan Linietsky <reduzio@gmail.com>
*/
-class AnimationKeyEditor;
+class AnimationTrackEditor;
class AnimationPlayerEditorPlugin;
+
class AnimationPlayerEditor : public VBoxContainer {
GDCLASS(AnimationPlayerEditor, VBoxContainer);
@@ -53,6 +54,14 @@ class AnimationPlayerEditor : public VBoxContainer {
AnimationPlayer *player;
enum {
+ TOOL_NEW_ANIM,
+ TOOL_LOAD_ANIM,
+ TOOL_SAVE_ANIM,
+ TOOL_SAVE_AS_ANIM,
+ TOOL_DUPLICATE_ANIM,
+ TOOL_RENAME_ANIM,
+ TOOL_EDIT_TRANSITIONS,
+ TOOL_REMOVE_ANIM,
TOOL_COPY_ANIM,
TOOL_PASTE_ANIM,
TOOL_EDIT_RESOURCE
@@ -72,6 +81,7 @@ class AnimationPlayerEditor : public VBoxContainer {
};
enum {
+ ANIM_OPEN,
ANIM_SAVE,
ANIM_SAVE_AS
};
@@ -89,16 +99,8 @@ class AnimationPlayerEditor : public VBoxContainer {
Button *play_bw_from;
//Button *pause;
- Button *add_anim;
Button *autoplay;
- Button *rename_anim;
- Button *duplicate_anim;
-
- Button *resource_edit_anim;
- Button *load_anim;
- MenuButton *save_anim;
- Button *blend_anim;
- Button *remove_anim;
+
MenuButton *tool_anim;
MenuButton *onion_skinning;
ToolButton *pin;
@@ -130,7 +132,7 @@ class AnimationPlayerEditor : public VBoxContainer {
bool updating;
bool updating_blends;
- AnimationKeyEditor *key_editor;
+ AnimationTrackEditor *track_editor;
// Onion skinning
struct {
@@ -207,8 +209,8 @@ class AnimationPlayerEditor : public VBoxContainer {
void _unhandled_key_input(const Ref<InputEvent> &p_ev);
void _animation_tool_menu(int p_option);
- void _animation_save_menu(int p_option);
void _onion_skinning_menu(int p_option);
+ void _animation_about_to_show_menu();
void _editor_visibility_changed();
bool _are_onion_layers_valid();
@@ -219,6 +221,8 @@ class AnimationPlayerEditor : public VBoxContainer {
void _start_onion_skinning();
void _stop_onion_skinning();
+ void _pin_pressed();
+
AnimationPlayerEditor();
~AnimationPlayerEditor();
@@ -232,7 +236,9 @@ public:
AnimationPlayer *get_player() const;
static AnimationPlayerEditor *singleton;
- AnimationKeyEditor *get_key_editor() { return key_editor; }
+ bool is_pinned() const { return pin->is_pressed(); }
+ void unpin() { pin->set_pressed(false); }
+ AnimationTrackEditor *get_track_editor() { return track_editor; }
Dictionary get_state() const;
void set_state(const Dictionary &p_state);
diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp
new file mode 100644
index 0000000000..04bd5f0cec
--- /dev/null
+++ b/editor/plugins/animation_state_machine_editor.cpp
@@ -0,0 +1,1313 @@
+#include "animation_state_machine_editor.h"
+
+#include "core/io/resource_loader.h"
+#include "core/project_settings.h"
+#include "math/delaunay.h"
+#include "os/input.h"
+#include "os/keyboard.h"
+#include "scene/animation/animation_blend_tree.h"
+#include "scene/animation/animation_player.h"
+#include "scene/gui/menu_button.h"
+#include "scene/gui/panel.h"
+#include "scene/main/viewport.h"
+
+void AnimationNodeStateMachineEditor::edit(AnimationNodeStateMachine *p_state_machine) {
+
+ if (state_machine.is_valid()) {
+ state_machine->disconnect("removed_from_graph", this, "_removed_from_graph");
+ }
+
+ if (p_state_machine) {
+ state_machine = Ref<AnimationNodeStateMachine>(p_state_machine);
+ } else {
+ state_machine.unref();
+ }
+
+ if (state_machine.is_null()) {
+ hide();
+ } else {
+ state_machine->connect("removed_from_graph", this, "_removed_from_graph");
+
+ selected_transition_from = StringName();
+ selected_transition_to = StringName();
+ selected_node = StringName();
+ _update_mode();
+ _update_graph();
+ }
+}
+
+void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEvent> &p_event) {
+
+ Ref<InputEventKey> k = p_event;
+ if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_scancode() == KEY_DELETE && !k->is_echo()) {
+ if (selected_node != StringName() || selected_transition_to != StringName() || selected_transition_from != StringName()) {
+ _erase_selected();
+ accept_event();
+ }
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+
+ //Add new node
+ if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) || (tool_create->is_pressed() && mb->get_button_index() == BUTTON_LEFT))) {
+ menu->clear();
+ animations_menu->clear();
+ animations_to_add.clear();
+ List<StringName> classes;
+ classes.sort_custom<StringName::AlphCompare>();
+
+ ClassDB::get_inheriters_from_class("AnimationRootNode", &classes);
+ menu->add_submenu_item(TTR("Add Animation"), "animations");
+
+ AnimationTree *gp = state_machine->get_tree();
+ ERR_FAIL_COND(!gp);
+ if (gp && gp->has_node(gp->get_animation_player())) {
+ AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player()));
+ if (ap) {
+ List<StringName> names;
+ ap->get_animation_list(&names);
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ animations_menu->add_icon_item(get_icon("Animation", "EditorIcons"), E->get());
+ animations_to_add.push_back(E->get());
+ }
+ }
+ }
+
+ for (List<StringName>::Element *E = classes.front(); E; E = E->next()) {
+
+ String name = String(E->get()).replace_first("AnimationNode", "");
+ if (name == "Animation")
+ continue; // nope
+ int idx = menu->get_item_count();
+ menu->add_item(vformat("Add %s", name));
+ menu->set_item_metadata(idx, E->get());
+ }
+
+ menu->set_global_position(state_machine_draw->get_global_transform().xform(mb->get_position()));
+ menu->popup();
+ add_node_pos = mb->get_position() / EDSCALE + state_machine->get_graph_offset();
+ }
+
+ // select node or push a field inside
+ if (mb.is_valid() && !mb->get_shift() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
+
+ selected_transition_from = StringName();
+ selected_transition_to = StringName();
+ selected_node = StringName();
+
+ for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order
+
+ if (node_rects[i].play.has_point(mb->get_position())) { //edit name
+ if (play_mode->get_selected() == 1 || !state_machine->is_playing()) {
+ //start
+ state_machine->start(node_rects[i].node_name);
+ } else {
+ //travel
+ if (!state_machine->travel(node_rects[i].node_name)) {
+
+ state_machine->start(node_rects[i].node_name);
+ //removing this due to usability..
+ //error_time = 5;
+ //error_text = vformat(TTR("No path found from '%s' to '%s'."), state_machine->get_current_node(), node_rects[i].node_name);
+ }
+ }
+ state_machine_draw->update();
+ return;
+ }
+
+ if (node_rects[i].name.has_point(mb->get_position())) { //edit name
+
+ Ref<StyleBox> line_sb = get_stylebox("normal", "LineEdit");
+
+ Rect2 edit_rect = node_rects[i].name;
+ edit_rect.position -= line_sb->get_offset();
+ edit_rect.size += line_sb->get_minimum_size();
+
+ name_edit->set_global_position(state_machine_draw->get_global_transform().xform(edit_rect.position));
+ name_edit->set_size(edit_rect.size);
+ name_edit->set_text(node_rects[i].node_name);
+ name_edit->show_modal();
+ name_edit->grab_focus();
+ name_edit->select_all();
+
+ prev_name = node_rects[i].node_name;
+ return;
+ }
+
+ if (node_rects[i].edit.has_point(mb->get_position())) { //edit name
+ call_deferred("_open_editor", node_rects[i].node_name);
+ return;
+ }
+
+ if (node_rects[i].node.has_point(mb->get_position())) { //select node since nothing else was selected
+ selected_node = node_rects[i].node_name;
+
+ Ref<AnimationNode> anode = state_machine->get_node(selected_node);
+ EditorNode::get_singleton()->push_item(anode.ptr(), "", true);
+ state_machine_draw->update();
+ dragging_selected_attempt = true;
+ dragging_selected = false;
+ drag_from = mb->get_position();
+ snap_x = StringName();
+ snap_y = StringName();
+ _update_mode();
+ return;
+ }
+ }
+
+ //test the lines now
+ int closest = -1;
+ float closest_d = 1e20;
+ for (int i = 0; i < transition_lines.size(); i++) {
+
+ Vector2 s[2] = {
+ transition_lines[i].from,
+ transition_lines[i].to
+ };
+ Vector2 cpoint = Geometry::get_closest_point_to_segment_2d(mb->get_position(), s);
+ float d = cpoint.distance_to(mb->get_position());
+ if (d > transition_lines[i].width) {
+ continue;
+ }
+
+ if (d < closest_d) {
+ closest = i;
+ closest_d = d;
+ }
+ }
+
+ if (closest >= 0) {
+ selected_transition_from = transition_lines[closest].from_node;
+ selected_transition_to = transition_lines[closest].to_node;
+
+ Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(closest);
+ EditorNode::get_singleton()->push_item(tr.ptr(), "", true);
+ }
+
+ state_machine_draw->update();
+ _update_mode();
+ }
+
+ //end moving node
+ if (mb.is_valid() && dragging_selected_attempt && mb->get_button_index() == BUTTON_LEFT && !mb->is_pressed()) {
+
+ if (dragging_selected) {
+
+ Ref<AnimationNode> an = state_machine->get_node(selected_node);
+ updating = true;
+ undo_redo->create_action("Move Node");
+ undo_redo->add_do_method(an.ptr(), "set_position", an->get_position() + drag_ofs / EDSCALE);
+ undo_redo->add_undo_method(an.ptr(), "set_position", an->get_position());
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+ }
+ snap_x = StringName();
+ snap_y = StringName();
+
+ dragging_selected_attempt = false;
+ dragging_selected = false;
+ state_machine_draw->update();
+ }
+
+ //connect nodes
+ if (mb.is_valid() && ((tool_select->is_pressed() && mb->get_shift()) || tool_connect->is_pressed()) && mb->get_button_index() == BUTTON_LEFT && mb->is_pressed()) {
+
+ for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order
+ if (node_rects[i].node.has_point(mb->get_position())) { //select node since nothing else was selected
+ connecting = true;
+ connecting_from = node_rects[i].node_name;
+ connecting_to = mb->get_position();
+ connecting_to_node = StringName();
+ return;
+ }
+ }
+ }
+
+ //end connecting nodes
+ if (mb.is_valid() && connecting && mb->get_button_index() == BUTTON_LEFT && !mb->is_pressed()) {
+
+ if (connecting_to_node != StringName()) {
+
+ if (state_machine->has_transition(connecting_from, connecting_to_node)) {
+ EditorNode::get_singleton()->show_warning("Transition exists!");
+
+ } else {
+
+ Ref<AnimationNodeStateMachineTransition> tr;
+ tr.instance();
+ tr->set_switch_mode(AnimationNodeStateMachineTransition::SwitchMode(transition_mode->get_selected()));
+
+ updating = true;
+ undo_redo->create_action("Add Transition");
+ undo_redo->add_do_method(state_machine.ptr(), "add_transition", connecting_from, connecting_to_node, tr);
+ undo_redo->add_undo_method(state_machine.ptr(), "remove_transition", connecting_from, connecting_to_node);
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+
+ selected_transition_from = connecting_from;
+ selected_transition_to = connecting_to_node;
+
+ EditorNode::get_singleton()->push_item(tr.ptr(), "", true);
+ _update_mode();
+ }
+ }
+ connecting_to_node = StringName();
+ connecting = false;
+ state_machine_draw->update();
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+
+ //pan window
+ if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_MIDDLE) {
+
+ h_scroll->set_value(h_scroll->get_value() - mm->get_relative().x);
+ v_scroll->set_value(v_scroll->get_value() - mm->get_relative().y);
+ }
+
+ //move mouse while connecting
+ if (mm.is_valid() && connecting) {
+
+ connecting_to = mm->get_position();
+ connecting_to_node = StringName();
+ state_machine_draw->update();
+
+ for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order
+ if (node_rects[i].node_name != connecting_from && node_rects[i].node.has_point(connecting_to)) { //select node since nothing else was selected
+ connecting_to_node = node_rects[i].node_name;
+ return;
+ }
+ }
+ }
+
+ //move mouse while moving a node
+ if (mm.is_valid() && dragging_selected_attempt) {
+
+ dragging_selected = true;
+ drag_ofs = mm->get_position() - drag_from;
+ snap_x = StringName();
+ snap_y = StringName();
+ {
+ //snap
+ Vector2 cpos = state_machine->get_node(selected_node)->get_position() + drag_ofs / EDSCALE;
+ List<StringName> nodes;
+ state_machine->get_node_list(&nodes);
+
+ float best_d_x = 1e20;
+ float best_d_y = 1e20;
+
+ for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) {
+ if (E->get() == selected_node)
+ continue;
+ Vector2 npos = state_machine->get_node(E->get())->get_position();
+
+ float d_x = ABS(npos.x - cpos.x);
+ if (d_x < MIN(5, best_d_x)) {
+ drag_ofs.x -= cpos.x - npos.x;
+ best_d_x = d_x;
+ snap_x = E->get();
+ }
+
+ float d_y = ABS(npos.y - cpos.y);
+ if (d_y < MIN(5, best_d_y)) {
+ drag_ofs.y -= cpos.y - npos.y;
+ best_d_y = d_y;
+ snap_y = E->get();
+ }
+ }
+ }
+
+ state_machine_draw->update();
+ }
+
+ //put ibeam (text cursor) over names to make it clearer that they are editable
+ if (mm.is_valid()) {
+
+ state_machine_draw->grab_focus();
+
+ bool over_text_now = false;
+ String new_over_node = StringName();
+ int new_over_node_what = -1;
+ if (tool_select->is_pressed()) {
+
+ for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order
+
+ if (node_rects[i].name.has_point(mm->get_position())) {
+ over_text_now = true;
+ break;
+ }
+
+ if (node_rects[i].node.has_point(mm->get_position())) {
+ new_over_node = node_rects[i].node_name;
+ if (node_rects[i].play.has_point(mm->get_position())) {
+ new_over_node_what = 0;
+ }
+ if (node_rects[i].edit.has_point(mm->get_position())) {
+ new_over_node_what = 1;
+ }
+ }
+ }
+ }
+
+ if (new_over_node != over_node || new_over_node_what != over_node_what) {
+ over_node = new_over_node;
+ over_node_what = new_over_node_what;
+ state_machine_draw->update();
+ }
+
+ if (over_text != over_text_now) {
+
+ if (over_text_now) {
+ state_machine_draw->set_default_cursor_shape(CURSOR_IBEAM);
+ } else {
+ state_machine_draw->set_default_cursor_shape(CURSOR_ARROW);
+ }
+
+ over_text = over_text_now;
+ }
+ }
+}
+
+void AnimationNodeStateMachineEditor::_add_menu_type(int p_index) {
+
+ String type = menu->get_item_metadata(p_index);
+
+ Object *obj = ClassDB::instance(type);
+ ERR_FAIL_COND(!obj);
+ AnimationNode *an = Object::cast_to<AnimationNode>(obj);
+ ERR_FAIL_COND(!an);
+
+ Ref<AnimationNode> node(an);
+ node->set_position(add_node_pos);
+
+ String base_name = type.replace_first("AnimationNode", "");
+ int base = 1;
+ String name = base_name;
+ while (state_machine->has_node(name)) {
+ base++;
+ name = base_name + " " + itos(base);
+ }
+
+ updating = true;
+ undo_redo->create_action("Add Node");
+ undo_redo->add_do_method(state_machine.ptr(), "add_node", name, node);
+ undo_redo->add_undo_method(state_machine.ptr(), "remove_node", name);
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+
+ state_machine_draw->update();
+}
+
+void AnimationNodeStateMachineEditor::_add_animation_type(int p_index) {
+
+ Ref<AnimationNodeAnimation> anim;
+ anim.instance();
+
+ anim->set_animation(animations_to_add[p_index]);
+
+ String base_name = animations_to_add[p_index];
+ int base = 1;
+ String name = base_name;
+ while (state_machine->has_node(name)) {
+ base++;
+ name = base_name + " " + itos(base);
+ }
+
+ anim->set_position(add_node_pos);
+
+ updating = true;
+ undo_redo->create_action("Add Node");
+ undo_redo->add_do_method(state_machine.ptr(), "add_node", name, anim);
+ undo_redo->add_undo_method(state_machine.ptr(), "remove_node", name);
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+
+ state_machine_draw->update();
+}
+
+void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, bool p_auto_advance) {
+
+ Color linecolor = get_color("font_color", "Label");
+ Color icon_color(1, 1, 1);
+ Color accent = get_color("accent_color", "Editor");
+
+ if (!p_enabled) {
+ linecolor.a *= 0.2;
+ icon_color.a *= 0.2;
+ accent.a *= 0.6;
+ }
+
+ Ref<Texture> icons[6] = {
+ get_icon("TransitionImmediateBig", "EditorIcons"),
+ get_icon("TransitionSyncBig", "EditorIcons"),
+ get_icon("TransitionEndBig", "EditorIcons"),
+ get_icon("TransitionImmediateAutoBig", "EditorIcons"),
+ get_icon("TransitionSyncAutoBig", "EditorIcons"),
+ get_icon("TransitionEndAutoBig", "EditorIcons")
+ };
+
+ if (p_selected) {
+ state_machine_draw->draw_line(p_from, p_to, accent, 6, true);
+ }
+
+ if (p_travel) {
+ linecolor = accent;
+ linecolor.set_hsv(1.0, linecolor.get_s(), linecolor.get_v());
+ }
+ state_machine_draw->draw_line(p_from, p_to, linecolor, 2, true);
+
+ Ref<Texture> icon = icons[p_mode + (p_auto_advance ? 3 : 0)];
+
+ Transform2D xf;
+ xf.elements[0] = (p_to - p_from).normalized();
+ xf.elements[1] = xf.elements[0].tangent();
+ xf.elements[2] = (p_from + p_to) * 0.5 - xf.elements[1] * icon->get_height() * 0.5 - xf.elements[0] * icon->get_height() * 0.5;
+
+ state_machine_draw->draw_set_transform_matrix(xf);
+ state_machine_draw->draw_texture(icon, Vector2(), icon_color);
+ state_machine_draw->draw_set_transform_matrix(Transform2D());
+}
+
+void AnimationNodeStateMachineEditor::_clip_src_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect) {
+
+ if (r_to == r_from)
+ return;
+
+ //this could be optimized...
+ Vector2 n = (r_to - r_from).normalized();
+ while (p_rect.has_point(r_from)) {
+ r_from += n;
+ }
+}
+
+void AnimationNodeStateMachineEditor::_clip_dst_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect) {
+
+ if (r_to == r_from)
+ return;
+
+ //this could be optimized...
+ Vector2 n = (r_to - r_from).normalized();
+ while (p_rect.has_point(r_to)) {
+ r_to -= n;
+ }
+}
+
+void AnimationNodeStateMachineEditor::_state_machine_draw() {
+
+ Ref<StyleBox> style = get_stylebox("frame", "GraphNode");
+ Ref<StyleBox> style_selected = get_stylebox("selectedframe", "GraphNode");
+
+ Ref<Font> font = get_font("title_font", "GraphNode");
+ Color font_color = get_color("title_color", "GraphNode");
+ Ref<Texture> play = get_icon("Play", "EditorIcons");
+ Ref<Texture> auto_play = get_icon("AutoPlay", "EditorIcons");
+ Ref<Texture> edit = get_icon("Edit", "EditorIcons");
+ Color accent = get_color("accent_color", "Editor");
+ Color linecolor = get_color("font_color", "Label");
+ linecolor.a *= 0.3;
+ Ref<StyleBox> playing_overlay = get_stylebox("position", "GraphNode");
+
+ bool playing = state_machine->is_playing();
+ StringName current = state_machine->get_current_node();
+ StringName blend_from = state_machine->get_blend_from_node();
+ Vector<StringName> travel_path = state_machine->get_travel_path();
+
+ if (state_machine_draw->has_focus()) {
+ state_machine_draw->draw_rect(Rect2(Point2(), state_machine_draw->get_size()), accent, false);
+ }
+ int sep = 3 * EDSCALE;
+
+ List<StringName> nodes;
+ state_machine->get_node_list(&nodes);
+
+ node_rects.clear();
+ Rect2 scroll_range(Point2(), state_machine_draw->get_size());
+
+ //snap lines
+ if (dragging_selected) {
+
+ Vector2 from = (state_machine->get_node(selected_node)->get_position() * EDSCALE) + drag_ofs - state_machine->get_graph_offset() * EDSCALE;
+ if (snap_x != StringName()) {
+ Vector2 to = (state_machine->get_node(snap_x)->get_position() * EDSCALE) - state_machine->get_graph_offset() * EDSCALE;
+ state_machine_draw->draw_line(from, to, linecolor, 2);
+ }
+ if (snap_y != StringName()) {
+ Vector2 to = (state_machine->get_node(snap_y)->get_position() * EDSCALE) - state_machine->get_graph_offset() * EDSCALE;
+ state_machine_draw->draw_line(from, to, linecolor, 2);
+ }
+ }
+
+ //pre pass nodes so we know the rectangles
+ for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) {
+
+ Ref<AnimationNode> anode = state_machine->get_node(E->get());
+ String name = E->get();
+ bool needs_editor = EditorNode::get_singleton()->item_has_editor(anode.ptr());
+ Ref<StyleBox> sb = E->get() == selected_node ? style_selected : style;
+
+ Size2 s = sb->get_minimum_size();
+ int strsize = font->get_string_size(name).width;
+ s.width += strsize;
+ s.height += MAX(font->get_height(), play->get_height());
+ s.width += sep + play->get_width();
+ if (needs_editor) {
+ s.width += sep + edit->get_width();
+ }
+
+ Vector2 offset;
+ offset += anode->get_position() * EDSCALE;
+ if (selected_node == E->get() && dragging_selected) {
+ offset += drag_ofs;
+ }
+ offset -= s / 2;
+ offset = offset.floor();
+
+ //prepre rect
+
+ NodeRect nr;
+ nr.node = Rect2(offset, s);
+ nr.node_name = E->get();
+
+ scroll_range = scroll_range.merge(nr.node); //merge with range
+
+ //now scroll it to draw
+ nr.node.position -= state_machine->get_graph_offset() * EDSCALE;
+
+ node_rects.push_back(nr);
+ }
+
+ transition_lines.clear();
+
+ //draw conecting line for potential new transition
+ if (connecting) {
+ Vector2 from = (state_machine->get_node(connecting_from)->get_position() * EDSCALE) - state_machine->get_graph_offset() * EDSCALE;
+ Vector2 to;
+ if (connecting_to_node != StringName()) {
+ to = (state_machine->get_node(connecting_to_node)->get_position() * EDSCALE) - state_machine->get_graph_offset() * EDSCALE;
+ } else {
+ to = connecting_to;
+ }
+
+ for (int i = 0; i < node_rects.size(); i++) {
+ if (node_rects[i].node_name == connecting_from) {
+ _clip_src_line_to_rect(from, to, node_rects[i].node);
+ }
+ if (node_rects[i].node_name == connecting_to_node) {
+ _clip_dst_line_to_rect(from, to, node_rects[i].node);
+ }
+ }
+
+ _connection_draw(from, to, AnimationNodeStateMachineTransition::SwitchMode(transition_mode->get_selected()), true, false, false, false);
+ }
+
+ Ref<Texture> tr_reference_icon = get_icon("TransitionImmediateBig", "EditorIcons");
+ float tr_bidi_offset = int(tr_reference_icon->get_height() * 0.8);
+
+ //draw transition lines
+ for (int i = 0; i < state_machine->get_transition_count(); i++) {
+
+ TransitionLine tl;
+ tl.from_node = state_machine->get_transition_from(i);
+ Vector2 ofs_from = (dragging_selected && tl.from_node == selected_node) ? drag_ofs : Vector2();
+ tl.from = (state_machine->get_node(tl.from_node)->get_position() * EDSCALE) + ofs_from - state_machine->get_graph_offset() * EDSCALE;
+
+ tl.to_node = state_machine->get_transition_to(i);
+ Vector2 ofs_to = (dragging_selected && tl.to_node == selected_node) ? drag_ofs : Vector2();
+ tl.to = (state_machine->get_node(tl.to_node)->get_position() * EDSCALE) + ofs_to - state_machine->get_graph_offset() * EDSCALE;
+
+ Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(i);
+ tl.disabled = tr->is_disabled();
+ tl.auto_advance = tr->has_auto_advance();
+ tl.mode = tr->get_switch_mode();
+ tl.width = tr_bidi_offset;
+
+ if (state_machine->has_transition(tl.to_node, tl.from_node)) { //offset if same exists
+ Vector2 offset = -(tl.from - tl.to).normalized().tangent() * tr_bidi_offset;
+ tl.from += offset;
+ tl.to += offset;
+ }
+
+ for (int i = 0; i < node_rects.size(); i++) {
+ if (node_rects[i].node_name == tl.from_node) {
+ _clip_src_line_to_rect(tl.from, tl.to, node_rects[i].node);
+ }
+ if (node_rects[i].node_name == tl.to_node) {
+ _clip_dst_line_to_rect(tl.from, tl.to, node_rects[i].node);
+ }
+ }
+
+ bool selected = selected_transition_from == tl.from_node && selected_transition_to == tl.to_node;
+
+ bool travel = false;
+
+ if (blend_from == tl.from_node && current == tl.to_node) {
+ travel = true;
+ }
+
+ if (travel_path.size()) {
+
+ if (current == tl.from_node && travel_path[0] == tl.to_node) {
+ travel = true;
+ } else {
+ for (int j = 0; j < travel_path.size() - 1; j++) {
+ if (travel_path[j] == tl.from_node && travel_path[j + 1] == tl.to_node) {
+ travel = true;
+ break;
+ }
+ }
+ }
+ }
+ _connection_draw(tl.from, tl.to, tl.mode, !tl.disabled, selected, travel, tl.auto_advance);
+
+ transition_lines.push_back(tl);
+ }
+
+ //draw actual nodes
+ for (int i = 0; i < node_rects.size(); i++) {
+
+ String name = node_rects[i].node_name;
+ Ref<AnimationNode> anode = state_machine->get_node(name);
+ bool needs_editor = EditorNode::get_singleton()->item_has_editor(anode.ptr());
+ Ref<StyleBox> sb = name == selected_node ? style_selected : style;
+ int strsize = font->get_string_size(name).width;
+
+ NodeRect &nr = node_rects[i];
+
+ Vector2 offset = nr.node.position;
+ int h = nr.node.size.height;
+
+ //prepre rect
+
+ //now scroll it to draw
+ state_machine_draw->draw_style_box(sb, nr.node);
+
+ if (playing && (blend_from == name || current == name || travel_path.find(name) != -1)) {
+ state_machine_draw->draw_style_box(playing_overlay, nr.node);
+ }
+
+ bool onstart = state_machine->get_start_node() == name;
+ if (onstart) {
+ state_machine_draw->draw_string(font, offset + Vector2(0, -font->get_height() - 3 * EDSCALE + font->get_ascent()), TTR("Start"), font_color);
+ }
+
+ if (state_machine->get_end_node() == name) {
+
+ int endofs = nr.node.size.x - font->get_string_size(TTR("End")).x;
+ state_machine_draw->draw_string(font, offset + Vector2(endofs, -font->get_height() - 3 * EDSCALE + font->get_ascent()), TTR("End"), font_color);
+ }
+
+ offset.x += sb->get_offset().x;
+
+ nr.play.position = offset + Vector2(0, (h - play->get_height()) / 2).floor();
+ nr.play.size = play->get_size();
+
+ Ref<Texture> play_tex = onstart ? auto_play : play;
+
+ if (over_node == name && over_node_what == 0) {
+ state_machine_draw->draw_texture(play_tex, nr.play.position, accent);
+ } else {
+ state_machine_draw->draw_texture(play_tex, nr.play.position);
+ }
+ offset.x += sep + play->get_width();
+
+ nr.name.position = offset + Vector2(0, (h - font->get_height()) / 2).floor();
+ nr.name.size = Vector2(strsize, font->get_height());
+
+ state_machine_draw->draw_string(font, nr.name.position + Vector2(0, font->get_ascent()), name, font_color);
+ offset.x += strsize + sep;
+
+ if (needs_editor) {
+ nr.edit.position = offset + Vector2(0, (h - edit->get_height()) / 2).floor();
+ nr.edit.size = edit->get_size();
+
+ if (over_node == name && over_node_what == 1) {
+ state_machine_draw->draw_texture(edit, nr.edit.position, accent);
+ } else {
+ state_machine_draw->draw_texture(edit, nr.edit.position);
+ }
+ offset.x += sep + edit->get_width();
+ }
+ }
+
+ scroll_range = scroll_range.grow(200 * EDSCALE);
+
+ //adjust scrollbars
+ updating = true;
+ h_scroll->set_min(scroll_range.position.x);
+ h_scroll->set_max(scroll_range.position.x + scroll_range.size.x);
+ h_scroll->set_page(state_machine_draw->get_size().x);
+ h_scroll->set_value(state_machine->get_graph_offset().x);
+
+ v_scroll->set_min(scroll_range.position.y);
+ v_scroll->set_max(scroll_range.position.y + scroll_range.size.y);
+ v_scroll->set_page(state_machine_draw->get_size().y);
+ v_scroll->set_value(state_machine->get_graph_offset().y);
+ updating = false;
+
+ state_machine_play_pos->update();
+}
+
+void AnimationNodeStateMachineEditor::_state_machine_pos_draw() {
+
+ if (!state_machine->is_playing())
+ return;
+
+ int idx = -1;
+ for (int i = 0; node_rects.size(); i++) {
+ if (node_rects[i].node_name == state_machine->get_current_node()) {
+ idx = i;
+ break;
+ }
+ }
+
+ if (idx == -1)
+ return;
+
+ NodeRect &nr = node_rects[idx];
+
+ Vector2 from;
+ from.x = nr.play.position.x;
+ from.y = (nr.play.position.y + nr.play.size.y + nr.node.position.y + nr.node.size.y) * 0.5;
+
+ Vector2 to;
+ if (nr.edit.size.x) {
+ to.x = nr.edit.position.x + nr.edit.size.x;
+ } else {
+ to.x = nr.name.position.x + nr.name.size.x;
+ }
+ to.y = from.y;
+
+ float len = MAX(0.0001, state_machine->get_current_length());
+
+ float pos = CLAMP(state_machine->get_current_play_pos(), 0, len);
+ float c = pos / len;
+ Color fg = get_color("font_color", "Label");
+ Color bg = fg;
+ bg.a *= 0.3;
+
+ state_machine_play_pos->draw_line(from, to, bg, 2);
+
+ to = from.linear_interpolate(to, c);
+
+ state_machine_play_pos->draw_line(from, to, fg, 2);
+}
+
+void AnimationNodeStateMachineEditor::_update_graph() {
+
+ if (updating)
+ return;
+
+ updating = true;
+
+ if (state_machine->get_parent().is_valid()) {
+ goto_parent_hbox->show();
+ } else {
+ goto_parent_hbox->hide();
+ }
+
+ state_machine_draw->update();
+
+ updating = false;
+}
+
+void AnimationNodeStateMachineEditor::_notification(int p_what) {
+
+ if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
+ error_panel->add_style_override("panel", get_stylebox("bg", "Tree"));
+ error_label->add_color_override("font_color", get_color("error_color", "Editor"));
+ panel->add_style_override("panel", get_stylebox("bg", "Tree"));
+ goto_parent->set_icon(get_icon("MoveUp", "EditorIcons"));
+
+ tool_select->set_icon(get_icon("ToolSelect", "EditorIcons"));
+ tool_create->set_icon(get_icon("ToolAddNode", "EditorIcons"));
+ tool_connect->set_icon(get_icon("ToolConnect", "EditorIcons"));
+
+ transition_mode->clear();
+ transition_mode->add_icon_item(get_icon("TransitionImmediate", "EditorIcons"), TTR("Immediate"));
+ transition_mode->add_icon_item(get_icon("TransitionSync", "EditorIcons"), TTR("Sync"));
+ transition_mode->add_icon_item(get_icon("TransitionEnd", "EditorIcons"), TTR("At End"));
+
+ //force filter on those, so they deform better
+ get_icon("TransitionImmediateBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER);
+ get_icon("TransitionEndBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER);
+ get_icon("TransitionSyncBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER);
+ get_icon("TransitionImmediateAutoBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER);
+ get_icon("TransitionEndAutoBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER);
+ get_icon("TransitionSyncAutoBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER);
+
+ tool_erase->set_icon(get_icon("Remove", "EditorIcons"));
+ tool_autoplay->set_icon(get_icon("AutoPlay", "EditorIcons"));
+ tool_end->set_icon(get_icon("AutoEnd", "EditorIcons"));
+
+ play_mode->clear();
+ play_mode->add_icon_item(get_icon("PlayTravel", "EditorIcons"), TTR("Travel"));
+ play_mode->add_icon_item(get_icon("Play", "EditorIcons"), TTR("Immediate"));
+ }
+
+ if (p_what == NOTIFICATION_PROCESS) {
+
+ String error;
+
+ if (error_time > 0) {
+ error = error_text;
+ error_time -= get_process_delta_time();
+ } else if (!state_machine->get_tree()) {
+ error = TTR("StateMachine does not belong to an AnimationTree node.");
+ } else if (!state_machine->get_tree()->is_active()) {
+ error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails.");
+ } else if (state_machine->get_tree()->is_state_invalid()) {
+ error = state_machine->get_tree()->get_invalid_state_reason();
+ } else if (state_machine->get_parent().is_valid() && state_machine->get_parent()->is_class("AnimationNodeStateMachine")) {
+ if (state_machine->get_start_node() == StringName() || state_machine->get_end_node() == StringName()) {
+ error = TTR("Start and end nodes are needed for a sub-transition.");
+ }
+ }
+
+ if (error != error_label->get_text()) {
+ error_label->set_text(error);
+ if (error != String()) {
+ error_panel->show();
+ } else {
+ error_panel->hide();
+ }
+ }
+
+ for (int i = 0; i < transition_lines.size(); i++) {
+ int tidx = -1;
+ for (int j = 0; j < state_machine->get_transition_count(); j++) {
+ if (transition_lines[i].from_node == state_machine->get_transition_from(j) && transition_lines[i].to_node == state_machine->get_transition_to(j)) {
+ tidx = j;
+ break;
+ }
+ }
+
+ if (tidx == -1) { //missing transition, should redraw
+ state_machine_draw->update();
+ break;
+ }
+
+ if (transition_lines[i].disabled != state_machine->get_transition(tidx)->is_disabled()) {
+ state_machine_draw->update();
+ break;
+ }
+
+ if (transition_lines[i].auto_advance != state_machine->get_transition(tidx)->has_auto_advance()) {
+ state_machine_draw->update();
+ break;
+ }
+
+ if (transition_lines[i].mode != state_machine->get_transition(tidx)->get_switch_mode()) {
+ state_machine_draw->update();
+ break;
+ }
+ }
+
+ bool same_travel_path = true;
+ Vector<StringName> tp = state_machine->get_travel_path();
+
+ {
+
+ if (last_travel_path.size() != tp.size()) {
+ same_travel_path = false;
+ } else {
+ for (int i = 0; i < last_travel_path.size(); i++) {
+ if (last_travel_path[i] != tp[i]) {
+ same_travel_path = false;
+ break;
+ }
+ }
+ }
+ }
+
+ //update if travel state changed
+ if (!same_travel_path || last_active != state_machine->is_playing() || last_current_node != state_machine->get_current_node() || last_blend_from_node != state_machine->get_blend_from_node()) {
+
+ state_machine_draw->update();
+ last_travel_path = tp;
+ last_current_node = state_machine->get_current_node();
+ last_active = state_machine->is_playing();
+ last_blend_from_node = state_machine->get_blend_from_node();
+ state_machine_play_pos->update();
+ }
+
+ if (last_play_pos != state_machine->get_current_play_pos()) {
+
+ last_play_pos = state_machine->get_current_play_pos();
+ state_machine_play_pos->update();
+ }
+ }
+
+ if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
+ over_node = StringName();
+ }
+}
+
+void AnimationNodeStateMachineEditor::_open_editor(const String &p_name) {
+ Ref<AnimationNode> an = state_machine->get_node(p_name);
+ ERR_FAIL_COND(!an.is_valid());
+ EditorNode::get_singleton()->edit_item(an.ptr());
+}
+
+void AnimationNodeStateMachineEditor::_goto_parent() {
+
+ EditorNode::get_singleton()->edit_item(state_machine->get_parent().ptr());
+}
+
+void AnimationNodeStateMachineEditor::_removed_from_graph() {
+ EditorNode::get_singleton()->edit_item(NULL);
+}
+
+void AnimationNodeStateMachineEditor::_name_edited(const String &p_text) {
+
+ String new_name = p_text;
+
+ ERR_FAIL_COND(new_name == "" || new_name.find(".") != -1 || new_name.find("/") != -1)
+
+ ERR_FAIL_COND(new_name == prev_name);
+
+ String base_name = new_name;
+ int base = 1;
+ String name = base_name;
+ while (state_machine->has_node(name)) {
+ base++;
+ name = base_name + " " + itos(base);
+ }
+
+ updating = true;
+ undo_redo->create_action("Node Renamed");
+ undo_redo->add_do_method(state_machine.ptr(), "rename_node", prev_name, name);
+ undo_redo->add_undo_method(state_machine.ptr(), "rename_node", name, prev_name);
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+
+ state_machine_draw->update();
+
+ name_edit->hide();
+}
+
+void AnimationNodeStateMachineEditor::_scroll_changed(double) {
+ if (updating)
+ return;
+
+ state_machine->set_graph_offset(Vector2(h_scroll->get_value(), v_scroll->get_value()));
+ state_machine_draw->update();
+}
+
+void AnimationNodeStateMachineEditor::_erase_selected() {
+
+ if (selected_node != StringName() && state_machine->has_node(selected_node)) {
+ updating = true;
+ undo_redo->create_action("Node Removed");
+ undo_redo->add_do_method(state_machine.ptr(), "remove_node", selected_node);
+ undo_redo->add_undo_method(state_machine.ptr(), "add_node", selected_node, state_machine->get_node(selected_node));
+ for (int i = 0; i < state_machine->get_transition_count(); i++) {
+ String from = state_machine->get_transition_from(i);
+ String to = state_machine->get_transition_to(i);
+ if (from == selected_node || to == selected_node) {
+ undo_redo->add_undo_method(state_machine.ptr(), "add_transition", from, to, state_machine->get_transition(i));
+ }
+ }
+ if (String(state_machine->get_start_node()) == selected_node) {
+ undo_redo->add_undo_method(state_machine.ptr(), "set_start_node", selected_node);
+ }
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+ selected_node = StringName();
+ }
+
+ if (selected_transition_to != StringName() && selected_transition_from != StringName() && state_machine->has_transition(selected_transition_from, selected_transition_to)) {
+
+ Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(state_machine->find_transition(selected_transition_from, selected_transition_to));
+ updating = true;
+ undo_redo->create_action("Transition Removed");
+ undo_redo->add_do_method(state_machine.ptr(), "remove_transition", selected_transition_from, selected_transition_to);
+ undo_redo->add_undo_method(state_machine.ptr(), "add_transition", selected_transition_from, selected_transition_to, tr);
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+ selected_transition_from = StringName();
+ selected_transition_to = StringName();
+ }
+
+ state_machine_draw->update();
+}
+
+void AnimationNodeStateMachineEditor::_autoplay_selected() {
+
+ if (selected_node != StringName() && state_machine->has_node(selected_node)) {
+
+ StringName new_start_node;
+ if (state_machine->get_start_node() == selected_node) { //toggle it
+ new_start_node = StringName();
+ } else {
+ new_start_node = selected_node;
+ }
+
+ updating = true;
+ undo_redo->create_action("Set Start Node (Autoplay)");
+ undo_redo->add_do_method(state_machine.ptr(), "set_start_node", new_start_node);
+ undo_redo->add_undo_method(state_machine.ptr(), "set_start_node", state_machine->get_start_node());
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+ state_machine_draw->update();
+ }
+}
+
+void AnimationNodeStateMachineEditor::_end_selected() {
+
+ if (selected_node != StringName() && state_machine->has_node(selected_node)) {
+
+ StringName new_end_node;
+ if (state_machine->get_end_node() == selected_node) { //toggle it
+ new_end_node = StringName();
+ } else {
+ new_end_node = selected_node;
+ }
+
+ updating = true;
+ undo_redo->create_action("Set Start Node (Autoplay)");
+ undo_redo->add_do_method(state_machine.ptr(), "set_end_node", new_end_node);
+ undo_redo->add_undo_method(state_machine.ptr(), "set_end_node", state_machine->get_end_node());
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+ state_machine_draw->update();
+ }
+}
+void AnimationNodeStateMachineEditor::_update_mode() {
+
+ if (tool_select->is_pressed()) {
+ tool_erase_hb->show();
+ tool_erase->set_disabled(selected_node == StringName() && selected_transition_from == StringName() && selected_transition_to == StringName());
+ tool_autoplay->set_disabled(selected_node == StringName());
+ tool_end->set_disabled(selected_node == StringName());
+ } else {
+ tool_erase_hb->hide();
+ }
+}
+
+void AnimationNodeStateMachineEditor::_bind_methods() {
+
+ ClassDB::bind_method("_state_machine_gui_input", &AnimationNodeStateMachineEditor::_state_machine_gui_input);
+ ClassDB::bind_method("_state_machine_draw", &AnimationNodeStateMachineEditor::_state_machine_draw);
+ ClassDB::bind_method("_state_machine_pos_draw", &AnimationNodeStateMachineEditor::_state_machine_pos_draw);
+ ClassDB::bind_method("_update_graph", &AnimationNodeStateMachineEditor::_update_graph);
+
+ ClassDB::bind_method("_add_menu_type", &AnimationNodeStateMachineEditor::_add_menu_type);
+ ClassDB::bind_method("_add_animation_type", &AnimationNodeStateMachineEditor::_add_animation_type);
+
+ ClassDB::bind_method("_name_edited", &AnimationNodeStateMachineEditor::_name_edited);
+
+ ClassDB::bind_method("_goto_parent", &AnimationNodeStateMachineEditor::_goto_parent);
+ ClassDB::bind_method("_removed_from_graph", &AnimationNodeStateMachineEditor::_removed_from_graph);
+
+ ClassDB::bind_method("_open_editor", &AnimationNodeStateMachineEditor::_open_editor);
+ ClassDB::bind_method("_scroll_changed", &AnimationNodeStateMachineEditor::_scroll_changed);
+
+ ClassDB::bind_method("_erase_selected", &AnimationNodeStateMachineEditor::_erase_selected);
+ ClassDB::bind_method("_autoplay_selected", &AnimationNodeStateMachineEditor::_autoplay_selected);
+ ClassDB::bind_method("_end_selected", &AnimationNodeStateMachineEditor::_end_selected);
+ ClassDB::bind_method("_update_mode", &AnimationNodeStateMachineEditor::_update_mode);
+}
+
+AnimationNodeStateMachineEditor *AnimationNodeStateMachineEditor::singleton = NULL;
+
+AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() {
+
+ singleton = this;
+ updating = false;
+
+ HBoxContainer *top_hb = memnew(HBoxContainer);
+ add_child(top_hb);
+
+ goto_parent_hbox = memnew(HBoxContainer);
+ goto_parent = memnew(ToolButton);
+ goto_parent->connect("pressed", this, "_goto_parent", varray(), CONNECT_DEFERRED);
+ goto_parent_hbox->add_child(goto_parent);
+ goto_parent_hbox->add_child(memnew(VSeparator));
+ top_hb->add_child(goto_parent_hbox);
+
+ Ref<ButtonGroup> bg;
+ bg.instance();
+
+ tool_select = memnew(ToolButton);
+ top_hb->add_child(tool_select);
+ tool_select->set_toggle_mode(true);
+ tool_select->set_button_group(bg);
+ tool_select->set_pressed(true);
+ tool_select->set_tooltip(TTR("Select and move nodes.\nRMB to add new nodes.\nShift+LMB to create connections."));
+ tool_select->connect("pressed", this, "_update_mode", varray(), CONNECT_DEFERRED);
+
+ tool_create = memnew(ToolButton);
+ top_hb->add_child(tool_create);
+ tool_create->set_toggle_mode(true);
+ tool_create->set_button_group(bg);
+ tool_create->set_tooltip(TTR("Create new nodes."));
+ tool_create->connect("pressed", this, "_update_mode", varray(), CONNECT_DEFERRED);
+
+ tool_connect = memnew(ToolButton);
+ top_hb->add_child(tool_connect);
+ tool_connect->set_toggle_mode(true);
+ tool_connect->set_button_group(bg);
+ tool_connect->set_tooltip(TTR("Connect nodes."));
+ tool_connect->connect("pressed", this, "_update_mode", varray(), CONNECT_DEFERRED);
+
+ tool_erase_hb = memnew(HBoxContainer);
+ top_hb->add_child(tool_erase_hb);
+ tool_erase_hb->add_child(memnew(VSeparator));
+ tool_erase = memnew(ToolButton);
+ tool_erase->set_tooltip(TTR("Remove selected node or transition"));
+ tool_erase_hb->add_child(tool_erase);
+ tool_erase->connect("pressed", this, "_erase_selected");
+ tool_erase->set_disabled(true);
+
+ tool_erase_hb->add_child(memnew(VSeparator));
+
+ tool_autoplay = memnew(ToolButton);
+ tool_autoplay->set_tooltip(TTR("Toggle autoplay this animation on start, restart or seek to zero."));
+ tool_erase_hb->add_child(tool_autoplay);
+ tool_autoplay->connect("pressed", this, "_autoplay_selected");
+ tool_autoplay->set_disabled(true);
+
+ tool_end = memnew(ToolButton);
+ tool_end->set_tooltip(TTR("Set the end animation. This is useful for sub-transitions."));
+ tool_erase_hb->add_child(tool_end);
+ tool_end->connect("pressed", this, "_end_selected");
+ tool_end->set_disabled(true);
+
+ top_hb->add_child(memnew(VSeparator));
+ top_hb->add_child(memnew(Label(TTR("Transition: "))));
+ transition_mode = memnew(OptionButton);
+ top_hb->add_child(transition_mode);
+
+ top_hb->add_spacer();
+
+ top_hb->add_child(memnew(Label("Play Mode:")));
+ play_mode = memnew(OptionButton);
+ top_hb->add_child(play_mode);
+
+ GridContainer *main_grid = memnew(GridContainer);
+ main_grid->set_columns(2);
+ add_child(main_grid);
+ main_grid->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ panel = memnew(PanelContainer);
+ panel->set_clip_contents(true);
+ main_grid->add_child(panel);
+ panel->set_h_size_flags(SIZE_EXPAND_FILL);
+
+ state_machine_draw = memnew(Control);
+ state_machine_draw->connect("gui_input", this, "_state_machine_gui_input");
+ state_machine_draw->connect("draw", this, "_state_machine_draw");
+ state_machine_draw->set_focus_mode(FOCUS_ALL);
+
+ state_machine_play_pos = memnew(Control);
+ state_machine_draw->add_child(state_machine_play_pos);
+ state_machine_play_pos->set_mouse_filter(MOUSE_FILTER_PASS); //pass all to parent
+ state_machine_play_pos->set_anchors_and_margins_preset(PRESET_WIDE);
+ state_machine_play_pos->connect("draw", this, "_state_machine_pos_draw");
+
+ panel->add_child(state_machine_draw);
+ panel->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ v_scroll = memnew(VScrollBar);
+ main_grid->add_child(v_scroll);
+ v_scroll->connect("value_changed", this, "_scroll_changed");
+
+ h_scroll = memnew(HScrollBar);
+ main_grid->add_child(h_scroll);
+ h_scroll->connect("value_changed", this, "_scroll_changed");
+
+ main_grid->add_child(memnew(Control)); //empty bottom right
+
+ error_panel = memnew(PanelContainer);
+ add_child(error_panel);
+ error_label = memnew(Label);
+ error_panel->add_child(error_label);
+ error_label->set_text("eh");
+
+ undo_redo = EditorNode::get_singleton()->get_undo_redo();
+
+ set_custom_minimum_size(Size2(0, 300 * EDSCALE));
+
+ menu = memnew(PopupMenu);
+ add_child(menu);
+ menu->connect("index_pressed", this, "_add_menu_type");
+
+ animations_menu = memnew(PopupMenu);
+ menu->add_child(animations_menu);
+ animations_menu->set_name("animations");
+ animations_menu->connect("index_pressed", this, "_add_animation_type");
+
+ name_edit = memnew(LineEdit);
+ state_machine_draw->add_child(name_edit);
+ name_edit->hide();
+ name_edit->connect("text_entered", this, "_name_edited");
+ name_edit->set_as_toplevel(true);
+
+ over_text = false;
+
+ over_node_what = -1;
+ dragging_selected_attempt = false;
+ connecting = false;
+
+ last_active = false;
+
+ error_time = 0;
+}
+
+void AnimationNodeStateMachineEditorPlugin::edit(Object *p_object) {
+
+ anim_tree_editor->edit(Object::cast_to<AnimationNodeStateMachine>(p_object));
+}
+
+bool AnimationNodeStateMachineEditorPlugin::handles(Object *p_object) const {
+
+ return p_object->is_class("AnimationNodeStateMachine");
+}
+
+void AnimationNodeStateMachineEditorPlugin::make_visible(bool p_visible) {
+
+ if (p_visible) {
+ //editor->hide_animation_player_editors();
+ //editor->animation_panel_make_visible(true);
+ button->show();
+ editor->make_bottom_panel_item_visible(anim_tree_editor);
+ anim_tree_editor->set_process(true);
+ } else {
+
+ if (anim_tree_editor->is_visible_in_tree())
+ editor->hide_bottom_panel();
+ button->hide();
+ anim_tree_editor->set_process(false);
+ }
+}
+
+AnimationNodeStateMachineEditorPlugin::AnimationNodeStateMachineEditorPlugin(EditorNode *p_node) {
+
+ editor = p_node;
+ anim_tree_editor = memnew(AnimationNodeStateMachineEditor);
+ anim_tree_editor->set_custom_minimum_size(Size2(0, 300));
+
+ button = editor->add_bottom_panel_item(TTR("StateMachine"), anim_tree_editor);
+ button->hide();
+}
+
+AnimationNodeStateMachineEditorPlugin::~AnimationNodeStateMachineEditorPlugin() {
+}
diff --git a/editor/plugins/animation_state_machine_editor.h b/editor/plugins/animation_state_machine_editor.h
new file mode 100644
index 0000000000..efd3de7415
--- /dev/null
+++ b/editor/plugins/animation_state_machine_editor.h
@@ -0,0 +1,167 @@
+#ifndef ANIMATION_STATE_MACHINE_EDITOR_H
+#define ANIMATION_STATE_MACHINE_EDITOR_H
+
+#include "editor/editor_node.h"
+#include "editor/editor_plugin.h"
+#include "editor/property_editor.h"
+#include "scene/animation/animation_node_state_machine.h"
+#include "scene/gui/button.h"
+#include "scene/gui/graph_edit.h"
+#include "scene/gui/popup.h"
+#include "scene/gui/tree.h"
+
+class AnimationNodeStateMachineEditor : public VBoxContainer {
+
+ GDCLASS(AnimationNodeStateMachineEditor, VBoxContainer);
+
+ Ref<AnimationNodeStateMachine> state_machine;
+
+ ToolButton *tool_select;
+ ToolButton *tool_create;
+ ToolButton *tool_connect;
+ LineEdit *name_edit;
+
+ HBoxContainer *tool_erase_hb;
+ ToolButton *tool_erase;
+ ToolButton *tool_autoplay;
+ ToolButton *tool_end;
+
+ OptionButton *transition_mode;
+ OptionButton *play_mode;
+
+ HBoxContainer *goto_parent_hbox;
+ ToolButton *goto_parent;
+
+ PanelContainer *panel;
+
+ StringName selected_node;
+
+ HScrollBar *h_scroll;
+ VScrollBar *v_scroll;
+
+ Control *state_machine_draw;
+ Control *state_machine_play_pos;
+
+ PanelContainer *error_panel;
+ Label *error_label;
+
+ bool updating;
+
+ UndoRedo *undo_redo;
+
+ static AnimationNodeStateMachineEditor *singleton;
+
+ void _state_machine_gui_input(const Ref<InputEvent> &p_event);
+ void _connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, bool p_auto_advance);
+ void _state_machine_draw();
+ void _state_machine_pos_draw();
+
+ void _update_graph();
+
+ PopupMenu *menu;
+ PopupMenu *animations_menu;
+ Vector<String> animations_to_add;
+
+ Vector2 add_node_pos;
+
+ bool dragging_selected_attempt;
+ bool dragging_selected;
+ Vector2 drag_from;
+ Vector2 drag_ofs;
+ StringName snap_x;
+ StringName snap_y;
+
+ bool connecting;
+ StringName connecting_from;
+ Vector2 connecting_to;
+ StringName connecting_to_node;
+
+ void _add_menu_type(int p_index);
+ void _add_animation_type(int p_index);
+
+ void _goto_parent();
+
+ void _removed_from_graph();
+
+ struct NodeRect {
+ StringName node_name;
+ Rect2 node;
+ Rect2 play;
+ Rect2 name;
+ Rect2 edit;
+ };
+
+ Vector<NodeRect> node_rects;
+
+ struct TransitionLine {
+ StringName from_node;
+ StringName to_node;
+ Vector2 from;
+ Vector2 to;
+ AnimationNodeStateMachineTransition::SwitchMode mode;
+ bool disabled;
+ bool auto_advance;
+ float width;
+ };
+
+ Vector<TransitionLine> transition_lines;
+
+ StringName selected_transition_from;
+ StringName selected_transition_to;
+
+ bool over_text;
+ StringName over_node;
+ int over_node_what;
+
+ String prev_name;
+ void _name_edited(const String &p_text);
+ void _open_editor(const String &p_name);
+ void _scroll_changed(double);
+
+ void _clip_src_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect);
+ void _clip_dst_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect);
+
+ void _erase_selected();
+ void _update_mode();
+ void _autoplay_selected();
+ void _end_selected();
+
+ bool last_active;
+ StringName last_blend_from_node;
+ StringName last_current_node;
+ Vector<StringName> last_travel_path;
+ float last_play_pos;
+
+ float error_time;
+ String error_text;
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ static AnimationNodeStateMachineEditor *get_singleton() { return singleton; }
+ void edit(AnimationNodeStateMachine *p_state_machine);
+ AnimationNodeStateMachineEditor();
+};
+
+class AnimationNodeStateMachineEditorPlugin : public EditorPlugin {
+
+ GDCLASS(AnimationNodeStateMachineEditorPlugin, EditorPlugin);
+
+ AnimationNodeStateMachineEditor *anim_tree_editor;
+ EditorNode *editor;
+ Button *button;
+
+public:
+ virtual String get_name() const { return "StateMachine"; }
+ bool has_main_screen() const { return false; }
+ virtual void edit(Object *p_object);
+ virtual bool handles(Object *p_object) const;
+ virtual void make_visible(bool p_visible);
+
+ AnimationNodeStateMachineEditorPlugin(EditorNode *p_node);
+ ~AnimationNodeStateMachineEditorPlugin();
+};
+
+#endif // ANIMATION_STATE_MACHINE_EDITOR_H
diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp
index d595d4dd98..505dd4ab76 100644
--- a/editor/plugins/asset_library_editor_plugin.cpp
+++ b/editor/plugins/asset_library_editor_plugin.cpp
@@ -234,6 +234,7 @@ void EditorAssetLibraryItemDescription::_preview_click(int p_id) {
if (!preview_images[i].is_video) {
if (preview_images[i].image.is_valid()) {
preview->set_texture(preview_images[i].image);
+ minimum_size_changed();
}
} else {
_link_click(preview_images[i].video_link);
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 488f687515..7c4cd6cb3d 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -30,7 +30,6 @@
#include "canvas_item_editor_plugin.h"
-#include "editor/animation_editor.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/plugins/animation_player_editor_plugin.h"
@@ -365,7 +364,7 @@ Object *CanvasItemEditor::_get_editor_data(Object *p_what) {
void CanvasItemEditor::_keying_changed() {
- if (AnimationPlayerEditor::singleton->get_key_editor()->is_visible_in_tree())
+ if (AnimationPlayerEditor::singleton->get_track_editor()->is_visible_in_tree())
animation_hb->show();
else
animation_hb->hide();
@@ -1984,32 +1983,53 @@ bool CanvasItemEditor::_gui_input_hover(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseMotion> m = p_event;
if (m.is_valid()) {
- if (drag_type == DRAG_NONE && tool == TOOL_SELECT) {
- Point2 click = transform.affine_inverse().xform(m->get_position());
-
- //Checks if the hovered items changed, update the viewport if so
- Vector<_SelectResult> hovering_results_tmp;
- _get_canvas_items_at_pos(click, hovering_results_tmp);
- hovering_results_tmp.sort();
- bool changed = false;
- if (hovering_results.size() == hovering_results_tmp.size()) {
- for (int i = 0; i < hovering_results.size(); i++) {
- if (hovering_results[i].item != hovering_results_tmp[i].item) {
- changed = true;
- break;
- }
- }
- } else {
- changed = true;
- }
+ Point2 click = transform.affine_inverse().xform(m->get_position());
- if (changed) {
- hovering_results = hovering_results_tmp;
- viewport->update();
+ // Checks if the hovered items changed, update the viewport if so
+ Vector<_SelectResult> hovering_results_items;
+ _get_canvas_items_at_pos(click, hovering_results_items);
+ hovering_results_items.sort();
+
+ // Compute the nodes names and icon position
+ Vector<_HoverResult> hovering_results_tmp;
+ for (int i = 0; i < hovering_results_items.size(); i++) {
+ CanvasItem *canvas_item = hovering_results_items[i].item;
+
+ if (canvas_item->_edit_use_rect())
+ continue;
+
+ _HoverResult hover_result;
+ hover_result.position = canvas_item->get_global_transform_with_canvas().get_origin();
+ if (has_icon(canvas_item->get_class(), "EditorIcons"))
+ hover_result.icon = get_icon(canvas_item->get_class(), "EditorIcons");
+ else
+ hover_result.icon = get_icon("Object", "EditorIcons");
+ hover_result.name = canvas_item->get_name();
+
+ hovering_results_tmp.push_back(hover_result);
+ }
+
+ // Check if changed, if so, update.
+ bool changed = false;
+ if (hovering_results_tmp.size() == hovering_results.size()) {
+ for (int i = 0; i < hovering_results_tmp.size(); i++) {
+ _HoverResult a = hovering_results_tmp[i];
+ _HoverResult b = hovering_results[i];
+ if (a.icon != b.icon || a.name != b.name || a.position != b.position) {
+ changed = true;
+ break;
+ }
}
+ } else {
+ changed = true;
+ }
- return true;
+ if (changed) {
+ hovering_results = hovering_results_tmp;
+ viewport->update();
}
+
+ return true;
}
return false;
@@ -2689,6 +2709,9 @@ void CanvasItemEditor::_draw_bones() {
continue;
Node2D *from_node = Object::cast_to<Node2D>(ObjectDB::get_instance(E->key().from));
+ if (!from_node->is_visible_in_tree())
+ continue;
+
Vector<Color> colors;
if (from_node->has_meta("_edit_ik_")) {
colors.push_back(bone_ik_color);
@@ -2767,26 +2790,15 @@ void CanvasItemEditor::_draw_hover() {
List<Rect2> previous_rects;
for (int i = 0; i < hovering_results.size(); i++) {
- // Draw the node's name and icon
- CanvasItem *canvas_item = hovering_results[i].item;
- if (canvas_item->_edit_use_rect())
- continue;
+ Ref<Texture> node_icon = hovering_results[i].icon;
+ String node_name = hovering_results[i].name;
- Transform2D xform = transform * canvas_item->get_global_transform_with_canvas();
-
- // Get the resources
- Ref<Texture> node_icon;
- if (has_icon(canvas_item->get_class(), "EditorIcons"))
- node_icon = get_icon(canvas_item->get_class(), "EditorIcons");
- else
- node_icon = get_icon("Object", "EditorIcons");
Ref<Font> font = get_font("font", "Label");
- String node_name = canvas_item->get_name();
Size2 node_name_size = font->get_string_size(node_name);
Size2 item_size = Size2(node_icon->get_size().x + 4 + node_name_size.x, MAX(node_icon->get_size().y, node_name_size.y - 3));
- Point2 pos = xform.get_origin() - Point2(0, item_size.y) + (Point2(node_icon->get_size().x, -node_icon->get_size().y) / 4);
+ Point2 pos = transform.xform(hovering_results[i].position) - Point2(0, item_size.y) + (Point2(node_icon->get_size().x, -node_icon->get_size().y) / 4);
// Rectify the position to avoid overlaping items
for (List<Rect2>::Element *E = previous_rects.front(); E; E = E->next()) {
if (E->get().intersects(Rect2(pos, item_size))) {
@@ -2796,8 +2808,10 @@ void CanvasItemEditor::_draw_hover() {
previous_rects.push_back(Rect2(pos, item_size));
- // Draw the node icon and name
+ // Draw icon
viewport->draw_texture(node_icon, pos, Color(1.0, 1.0, 1.0, 0.5));
+
+ // Draw name
viewport->draw_string(font, pos + Point2(node_icon->get_size().x + 4, item_size.y - 3), node_name, Color(1.0, 1.0, 1.0, 0.5));
}
}
@@ -3077,7 +3091,7 @@ void CanvasItemEditor::_notification(int p_what) {
select_sb->set_default_margin(Margin(i), 4);
}
- AnimationPlayerEditor::singleton->get_key_editor()->connect("visibility_changed", this, "_keying_changed");
+ AnimationPlayerEditor::singleton->get_track_editor()->connect("visibility_changed", this, "_keying_changed");
_keying_changed();
get_tree()->connect("node_added", this, "_tree_changed", varray());
get_tree()->connect("node_removed", this, "_tree_changed", varray());
@@ -3689,11 +3703,11 @@ void CanvasItemEditor::_popup_callback(int p_op) {
Node2D *n2d = Object::cast_to<Node2D>(canvas_item);
if (key_pos)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), existing);
if (key_rot)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(n2d, "rotation_degrees", Math::rad2deg(n2d->get_rotation()), existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "rotation_degrees", Math::rad2deg(n2d->get_rotation()), existing);
if (key_scale)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), existing);
if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) {
//look for an IK chain
@@ -3720,11 +3734,11 @@ void CanvasItemEditor::_popup_callback(int p_op) {
for (List<Node2D *>::Element *F = ik_chain.front(); F; F = F->next()) {
if (key_pos)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(F->get(), "position", F->get()->get_position(), existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "position", F->get()->get_position(), existing);
if (key_rot)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(F->get(), "rotation_degrees", Math::rad2deg(F->get()->get_rotation()), existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "rotation_degrees", Math::rad2deg(F->get()->get_rotation()), existing);
if (key_scale)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(F->get(), "scale", F->get()->get_scale(), existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "scale", F->get()->get_scale(), existing);
}
}
}
@@ -3734,11 +3748,11 @@ void CanvasItemEditor::_popup_callback(int p_op) {
Control *ctrl = Object::cast_to<Control>(canvas_item);
if (key_pos)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), existing);
if (key_rot)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation_degrees(), existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation_degrees(), existing);
if (key_scale)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), existing);
}
}
@@ -3834,7 +3848,7 @@ void CanvasItemEditor::_popup_callback(int p_op) {
ctrl->set_position(Point2());
/*
if (key_scale)
- AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(ctrl,"rect/size",ctrl->get_size());
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl,"rect/size",ctrl->get_size());
*/
}
}
@@ -4336,13 +4350,13 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
hb->add_child(snap_button);
snap_button->set_toggle_mode(true);
snap_button->connect("toggled", this, "_button_toggle_snap");
- snap_button->set_tooltip(TTR("Toggles snapping"));
- snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_snap", TTR("Use Snap"), KEY_S));
+ snap_button->set_tooltip(TTR("Toggle snapping."));
+ snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_snap", TTR("Use Snap"), KEY_MASK_SHIFT | KEY_S));
snap_config_menu = memnew(MenuButton);
hb->add_child(snap_config_menu);
snap_config_menu->set_h_size_flags(SIZE_SHRINK_END);
- snap_config_menu->set_tooltip(TTR("Snapping options"));
+ snap_config_menu->set_tooltip(TTR("Snapping Options"));
PopupMenu *p = snap_config_menu->get_popup();
p->connect("id_pressed", this, "_popup_callback");
diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h
index 4d2af11303..adc4010f39 100644
--- a/editor/plugins/canvas_item_editor_plugin.h
+++ b/editor/plugins/canvas_item_editor_plugin.h
@@ -257,9 +257,15 @@ class CanvasItemEditor : public VBoxContainer {
return has_z && p_rr.has_z ? p_rr.z_index < z_index : p_rr.has_z;
}
};
-
Vector<_SelectResult> selection_results;
- Vector<_SelectResult> hovering_results;
+
+ struct _HoverResult {
+
+ Point2 position;
+ Ref<Texture> icon;
+ String name;
+ };
+ Vector<_HoverResult> hovering_results;
struct BoneList {
diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp
index d76c515c1f..0d25b3685a 100644
--- a/editor/plugins/editor_preview_plugins.cpp
+++ b/editor/plugins/editor_preview_plugins.cpp
@@ -37,6 +37,7 @@
#include "io/resource_loader.h"
#include "os/os.h"
#include "scene/resources/bit_mask.h"
+#include "scene/resources/dynamic_font.h"
#include "scene/resources/material.h"
#include "scene/resources/mesh.h"
@@ -96,6 +97,7 @@ Ref<Texture> EditorTexturePreviewPlugin::generate(const RES &p_from) {
if (img.is_null() || img->empty())
return Ref<Texture>();
+ img = img->duplicate();
img->clear_mipmaps();
int thumbnail_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size");
@@ -552,274 +554,92 @@ EditorScriptPreviewPlugin::EditorScriptPreviewPlugin() {
}
///////////////////////////////////////////////////////////////////
-// FIXME: Needs to be rewritten for AudioStream in Godot 3.0+
-#if 0
-bool EditorSamplePreviewPlugin::handles(const String& p_type) const {
+bool EditorAudioStreamPreviewPlugin::handles(const String &p_type) const {
- return ClassDB::is_parent_class(p_type,"Sample");
+ return ClassDB::is_parent_class(p_type, "AudioStream");
}
-Ref<Texture> EditorSamplePreviewPlugin::generate(const RES& p_from) {
-
- Ref<Sample> smp =p_from;
- ERR_FAIL_COND_V(smp.is_null(),Ref<Texture>());
+Ref<Texture> EditorAudioStreamPreviewPlugin::generate(const RES &p_from) {
+ Ref<AudioStream> stream = p_from;
+ ERR_FAIL_COND_V(stream.is_null(), Ref<Texture>());
int thumbnail_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size");
- thumbnail_size*=EDSCALE;
+ thumbnail_size *= EDSCALE;
PoolVector<uint8_t> img;
int w = thumbnail_size;
int h = thumbnail_size;
- img.resize(w*h*3);
+ img.resize(w * h * 3);
PoolVector<uint8_t>::Write imgdata = img.write();
- uint8_t * imgw = imgdata.ptr();
- PoolVector<uint8_t> data = smp->get_data();
- PoolVector<uint8_t>::Read sampledata = data.read();
- const uint8_t *sdata=sampledata.ptr();
+ uint8_t *imgw = imgdata.ptr();
- bool stereo = smp->is_stereo();
- bool _16=smp->get_format()==Sample::FORMAT_PCM16;
- int len = smp->get_length();
+ Ref<AudioStreamPlayback> playback = stream->instance_playback();
- if (len<1)
- return Ref<Texture>();
+ float len_s = stream->get_length();
+ if (len_s == 0) {
+ len_s = 60; //one minute audio if no length specified
+ }
+ int frame_length = AudioServer::get_singleton()->get_mix_rate() * len_s;
- if (smp->get_format()==Sample::FORMAT_IMA_ADPCM) {
-
- struct IMA_ADPCM_State {
-
- int16_t step_index;
- int32_t predictor;
- /* values at loop point */
- int16_t loop_step_index;
- int32_t loop_predictor;
- int32_t last_nibble;
- int32_t loop_pos;
- int32_t window_ofs;
- const uint8_t *ptr;
- } ima_adpcm;
-
- ima_adpcm.step_index=0;
- ima_adpcm.predictor=0;
- ima_adpcm.loop_step_index=0;
- ima_adpcm.loop_predictor=0;
- ima_adpcm.last_nibble=-1;
- ima_adpcm.loop_pos=0x7FFFFFFF;
- ima_adpcm.window_ofs=0;
- ima_adpcm.ptr=NULL;
-
-
- for(int i=0;i<w;i++) {
-
- float max[2]={-1e10,-1e10};
- float min[2]={1e10,1e10};
- int from = i*len/w;
- int to = (i+1)*len/w;
- if (to>=len)
- to=len-1;
-
- for(int j=from;j<to;j++) {
-
- while(j>ima_adpcm.last_nibble) {
-
- static const int16_t _ima_adpcm_step_table[89] = {
- 7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
- 19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
- 50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
- 130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
- 337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
- 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
- 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
- 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
- 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
- };
-
- static const int8_t _ima_adpcm_index_table[16] = {
- -1, -1, -1, -1, 2, 4, 6, 8,
- -1, -1, -1, -1, 2, 4, 6, 8
- };
-
- int16_t nibble,diff,step;
-
- ima_adpcm.last_nibble++;
- const uint8_t *src_ptr=sdata;
-
- int ofs = ima_adpcm.last_nibble>>1;
-
- if (stereo)
- ofs*=2;
-
-
- nibble = (ima_adpcm.last_nibble&1)?
- (src_ptr[ofs]>>4):(src_ptr[ofs]&0xF);
- step=_ima_adpcm_step_table[ima_adpcm.step_index];
-
- ima_adpcm.step_index += _ima_adpcm_index_table[nibble];
- if (ima_adpcm.step_index<0)
- ima_adpcm.step_index=0;
- if (ima_adpcm.step_index>88)
- ima_adpcm.step_index=88;
-
- diff = step >> 3 ;
- if (nibble & 1)
- diff += step >> 2 ;
- if (nibble & 2)
- diff += step >> 1 ;
- if (nibble & 4)
- diff += step ;
- if (nibble & 8)
- diff = -diff ;
-
- ima_adpcm.predictor+=diff;
- if (ima_adpcm.predictor<-0x8000)
- ima_adpcm.predictor=-0x8000;
- else if (ima_adpcm.predictor>0x7FFF)
- ima_adpcm.predictor=0x7FFF;
-
-
- /* store loop if there */
- if (ima_adpcm.last_nibble==ima_adpcm.loop_pos) {
-
- ima_adpcm.loop_step_index = ima_adpcm.step_index;
- ima_adpcm.loop_predictor = ima_adpcm.predictor;
- }
+ Vector<AudioFrame> frames;
+ frames.resize(frame_length);
- }
+ playback->start();
+ playback->mix(frames.ptrw(), 1, frames.size());
+ playback->stop();
- float v=ima_adpcm.predictor/32767.0;
- if (v>max[0])
- max[0]=v;
- if (v<min[0])
- min[0]=v;
- }
- max[0]*=0.8;
- min[0]*=0.8;
-
- for(int j=0;j<h;j++) {
- float v = (j/(float)h) * 2.0 - 1.0;
- uint8_t* imgofs = &imgw[(uint64_t(j)*w+i)*3];
- if (v>min[0] && v<max[0]) {
- imgofs[0]=255;
- imgofs[1]=150;
- imgofs[2]=80;
- } else {
- imgofs[0]=0;
- imgofs[1]=0;
- imgofs[2]=0;
- }
- }
- }
- } else {
- for(int i=0;i<w;i++) {
- // i trust gcc will optimize this loop
- float max[2]={-1e10,-1e10};
- float min[2]={1e10,1e10};
- int c=stereo?2:1;
- int from = uint64_t(i)*len/w;
- int to = (uint64_t(i)+1)*len/w;
- if (to>=len)
- to=len-1;
-
- if (_16) {
- const int16_t*src =(const int16_t*)sdata;
-
- for(int j=0;j<c;j++) {
-
- for(int k=from;k<=to;k++) {
-
- float v = src[uint64_t(k)*c+j]/32768.0;
- if (v>max[j])
- max[j]=v;
- if (v<min[j])
- min[j]=v;
- }
+ for (int i = 0; i < w; i++) {
- }
- } else {
-
- const int8_t*src =(const int8_t*)sdata;
+ float max = -1000;
+ float min = 1000;
+ int from = uint64_t(i) * frame_length / w;
+ int to = uint64_t(i + 1) * frame_length / w;
+ to = MIN(to, frame_length);
+ from = MIN(from, frame_length - 1);
+ if (to == from) {
+ to = from + 1;
+ }
- for(int j=0;j<c;j++) {
+ for (int j = from; j < to; j++) {
- for(int k=from;k<=to;k++) {
+ max = MAX(max, frames[j].l);
+ max = MAX(max, frames[j].r);
- float v = src[uint64_t(k)*c+j]/128.0;
- if (v>max[j])
- max[j]=v;
- if (v<min[j])
- min[j]=v;
- }
+ min = MIN(min, frames[j].l);
+ min = MIN(min, frames[j].r);
+ }
- }
- }
+ int pfrom = CLAMP((min * 0.5 + 0.5) * h / 2, 0, h / 2) + h / 4;
+ int pto = CLAMP((max * 0.5 + 0.5) * h / 2, 0, h / 2) + h / 4;
- max[0]*=0.8;
- max[1]*=0.8;
- min[0]*=0.8;
- min[1]*=0.8;
-
- if (!stereo) {
- for(int j=0;j<h;j++) {
- float v = (j/(float)h) * 2.0 - 1.0;
- uint8_t* imgofs = &imgw[(j*w+i)*3];
- if (v>min[0] && v<max[0]) {
- imgofs[0]=255;
- imgofs[1]=150;
- imgofs[2]=80;
- } else {
- imgofs[0]=0;
- imgofs[1]=0;
- imgofs[2]=0;
- }
- }
+ for (int j = 0; j < h; j++) {
+ uint8_t *p = &imgw[(j * w + i) * 3];
+ if (j < pfrom || j > pto) {
+ p[0] = 100;
+ p[1] = 100;
+ p[2] = 100;
} else {
-
- for(int j=0;j<h;j++) {
-
- int half;
- float v;
- if (j<(h/2)) {
- half=0;
- v = (j/(float)(h/2)) * 2.0 - 1.0;
- } else {
- half=1;
- if( (float)(h/2) != 0 ) {
- v = ((j-(h/2))/(float)(h/2)) * 2.0 - 1.0;
- } else {
- v = ((j-(h/2))/(float)(1/2)) * 2.0 - 1.0;
- }
- }
-
- uint8_t* imgofs = &imgw[(j*w+i)*3];
- if (v>min[half] && v<max[half]) {
- imgofs[0]=255;
- imgofs[1]=150;
- imgofs[2]=80;
- } else {
- imgofs[0]=0;
- imgofs[1]=0;
- imgofs[2]=0;
- }
- }
-
+ p[0] = 180;
+ p[1] = 180;
+ p[2] = 180;
}
-
}
}
imgdata = PoolVector<uint8_t>::Write();
- post_process_preview(img);
+ //post_process_preview(img);
- Ref<ImageTexture> ptex = Ref<ImageTexture>( memnew( ImageTexture));
- ptex->create_from_image(Image(w,h,0,Image::FORMAT_RGB8,img),0);
+ Ref<ImageTexture> ptex = Ref<ImageTexture>(memnew(ImageTexture));
+ Ref<Image> image;
+ image.instance();
+ image->create(w, h, false, Image::FORMAT_RGB8, img);
+ ptex->create_from_image(image, 0);
return ptex;
-
}
-EditorSamplePreviewPlugin::EditorSamplePreviewPlugin() {
+EditorAudioStreamPreviewPlugin::EditorAudioStreamPreviewPlugin() {
}
-#endif
///////////////////////////////////////////////////////////////////////////
@@ -933,3 +753,100 @@ EditorMeshPreviewPlugin::~EditorMeshPreviewPlugin() {
VS::get_singleton()->free(camera);
VS::get_singleton()->free(scenario);
}
+
+///////////////////////////////////////////////////////////////////////////
+
+void EditorFontPreviewPlugin::_preview_done(const Variant &p_udata) {
+
+ preview_done = true;
+}
+
+void EditorFontPreviewPlugin::_bind_methods() {
+
+ ClassDB::bind_method("_preview_done", &EditorFontPreviewPlugin::_preview_done);
+}
+
+bool EditorFontPreviewPlugin::handles(const String &p_type) const {
+
+ return ClassDB::is_parent_class(p_type, "DynamicFontData");
+}
+
+Ref<Texture> EditorFontPreviewPlugin::generate_from_path(const String &p_path) {
+ if (canvas.is_valid()) {
+ VS::get_singleton()->viewport_remove_canvas(viewport, canvas);
+ }
+
+ canvas = VS::get_singleton()->canvas_create();
+ canvas_item = VS::get_singleton()->canvas_item_create();
+
+ VS::get_singleton()->viewport_attach_canvas(viewport, canvas);
+ VS::get_singleton()->canvas_item_set_parent(canvas_item, canvas);
+
+ Ref<DynamicFontData> SampledFont;
+ SampledFont.instance();
+ SampledFont->set_font_path(p_path);
+
+ int thumbnail_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size");
+ thumbnail_size *= EDSCALE;
+
+ Ref<DynamicFont> sampled_font;
+ sampled_font.instance();
+ sampled_font->set_size(50);
+ sampled_font->set_font_data(SampledFont);
+
+ String sampled_text = "Abg";
+ Vector2 size = sampled_font->get_string_size(sampled_text);
+
+ Vector2 pos;
+
+ pos.x = 64 - size.x / 2;
+ pos.y = 80;
+
+ Ref<Font> font = sampled_font;
+
+ font->draw(canvas_item, pos, sampled_text);
+
+ VS::get_singleton()->viewport_set_update_mode(viewport, VS::VIEWPORT_UPDATE_ONCE); //once used for capture
+
+ preview_done = false;
+ VS::get_singleton()->request_frame_drawn_callback(this, "_preview_done", Variant());
+
+ while (!preview_done) {
+ OS::get_singleton()->delay_usec(10);
+ }
+
+ Ref<Image> img = VS::get_singleton()->VS::get_singleton()->texture_get_data(viewport_texture);
+ ERR_FAIL_COND_V(img.is_null(), Ref<ImageTexture>());
+
+ img->convert(Image::FORMAT_RGBA8);
+ img->resize(thumbnail_size, thumbnail_size);
+
+ post_process_preview(img);
+
+ Ref<ImageTexture> ptex = Ref<ImageTexture>(memnew(ImageTexture));
+ ptex->create_from_image(img, 0);
+
+ return ptex;
+}
+
+Ref<Texture> EditorFontPreviewPlugin::generate(const RES &p_from) {
+
+ return generate_from_path(p_from->get_path());
+}
+
+EditorFontPreviewPlugin::EditorFontPreviewPlugin() {
+
+ viewport = VS::get_singleton()->viewport_create();
+ VS::get_singleton()->viewport_set_update_mode(viewport, VS::VIEWPORT_UPDATE_DISABLED);
+ VS::get_singleton()->viewport_set_vflip(viewport, true);
+ VS::get_singleton()->viewport_set_size(viewport, 128, 128);
+ VS::get_singleton()->viewport_set_active(viewport, true);
+ viewport_texture = VS::get_singleton()->viewport_get_texture(viewport);
+}
+
+EditorFontPreviewPlugin::~EditorFontPreviewPlugin() {
+
+ VS::get_singleton()->free(canvas_item);
+ VS::get_singleton()->free(canvas);
+ VS::get_singleton()->free(viewport);
+}
diff --git a/editor/plugins/editor_preview_plugins.h b/editor/plugins/editor_preview_plugins.h
index 35b5c3a5f0..140d9f849f 100644
--- a/editor/plugins/editor_preview_plugins.h
+++ b/editor/plugins/editor_preview_plugins.h
@@ -100,17 +100,13 @@ public:
EditorScriptPreviewPlugin();
};
-// FIXME: Needs to be rewritten for AudioStream in Godot 3.0+
-#if 0
-class EditorSamplePreviewPlugin : public EditorResourcePreviewGenerator {
+class EditorAudioStreamPreviewPlugin : public EditorResourcePreviewGenerator {
public:
+ virtual bool handles(const String &p_type) const;
+ virtual Ref<Texture> generate(const RES &p_from);
- virtual bool handles(const String& p_type) const;
- virtual Ref<Texture> generate(const RES& p_from);
-
- EditorSamplePreviewPlugin();
+ EditorAudioStreamPreviewPlugin();
};
-#endif
class EditorMeshPreviewPlugin : public EditorResourcePreviewGenerator {
@@ -140,4 +136,27 @@ public:
~EditorMeshPreviewPlugin();
};
+class EditorFontPreviewPlugin : public EditorResourcePreviewGenerator {
+
+ GDCLASS(EditorFontPreviewPlugin, EditorResourcePreviewGenerator)
+
+ RID viewport;
+ RID viewport_texture;
+ RID canvas;
+ RID canvas_item;
+ volatile bool preview_done;
+
+ void _preview_done(const Variant &p_udata);
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual bool handles(const String &p_type) const;
+ virtual Ref<Texture> generate(const RES &p_from);
+ virtual Ref<Texture> generate_from_path(const String &p_path);
+
+ EditorFontPreviewPlugin();
+ ~EditorFontPreviewPlugin();
+};
#endif // EDITORPREVIEWPLUGINS_H
diff --git a/editor/plugins/navigation_mesh_editor_plugin.cpp b/editor/plugins/navigation_mesh_editor_plugin.cpp
deleted file mode 100644
index da3c744324..0000000000
--- a/editor/plugins/navigation_mesh_editor_plugin.cpp
+++ /dev/null
@@ -1,166 +0,0 @@
-/*************************************************************************/
-/* navigation_mesh_editor_plugin.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include "navigation_mesh_editor_plugin.h"
-#include "io/marshalls.h"
-#include "io/resource_saver.h"
-#include "scene/3d/mesh_instance.h"
-#include "scene/gui/box_container.h"
-
-#ifdef RECAST_ENABLED
-
-void NavigationMeshEditor::_node_removed(Node *p_node) {
-
- if (p_node == node) {
- node = NULL;
-
- hide();
- }
-}
-
-void NavigationMeshEditor::_notification(int p_option) {
-
- if (p_option == NOTIFICATION_ENTER_TREE) {
-
- button_bake->set_icon(get_icon("Bake", "EditorIcons"));
- button_reset->set_icon(get_icon("Reload", "EditorIcons"));
- }
-}
-
-void NavigationMeshEditor::_bake_pressed() {
-
- ERR_FAIL_COND(!node);
- const String conf_warning = node->get_configuration_warning();
- if (!conf_warning.empty()) {
- err_dialog->set_text(conf_warning);
- err_dialog->popup_centered_minsize();
- button_bake->set_pressed(false);
- return;
- }
-
- NavigationMeshGenerator::clear(node->get_navigation_mesh());
- NavigationMeshGenerator::bake(node->get_navigation_mesh(), node);
-
- if (node) {
- node->update_gizmo();
- }
-}
-
-void NavigationMeshEditor::_clear_pressed() {
-
- if (node)
- NavigationMeshGenerator::clear(node->get_navigation_mesh());
-
- button_bake->set_pressed(false);
- bake_info->set_text("");
-
- if (node) {
- node->update_gizmo();
- }
-}
-
-void NavigationMeshEditor::edit(NavigationMeshInstance *p_nav_mesh_instance) {
-
- if (p_nav_mesh_instance == NULL || node == p_nav_mesh_instance) {
- return;
- }
-
- node = p_nav_mesh_instance;
-}
-
-void NavigationMeshEditor::_bind_methods() {
-
- ClassDB::bind_method("_bake_pressed", &NavigationMeshEditor::_bake_pressed);
- ClassDB::bind_method("_clear_pressed", &NavigationMeshEditor::_clear_pressed);
-}
-
-NavigationMeshEditor::NavigationMeshEditor() {
-
- bake_hbox = memnew(HBoxContainer);
- button_bake = memnew(ToolButton);
- button_bake->set_text(TTR("Bake!"));
- button_bake->set_toggle_mode(true);
- button_reset = memnew(Button);
- button_bake->set_tooltip(TTR("Bake the navigation mesh.") + "\n");
-
- bake_info = memnew(Label);
- bake_hbox->add_child(button_bake);
- bake_hbox->add_child(button_reset);
- bake_hbox->add_child(bake_info);
-
- err_dialog = memnew(AcceptDialog);
- add_child(err_dialog);
- node = NULL;
-
- button_bake->connect("pressed", this, "_bake_pressed");
- button_reset->connect("pressed", this, "_clear_pressed");
- button_reset->set_tooltip(TTR("Clear the navigation mesh."));
-}
-
-NavigationMeshEditor::~NavigationMeshEditor() {
-}
-
-void NavigationMeshEditorPlugin::edit(Object *p_object) {
-
- navigation_mesh_editor->edit(Object::cast_to<NavigationMeshInstance>(p_object));
-}
-
-bool NavigationMeshEditorPlugin::handles(Object *p_object) const {
-
- return p_object->is_class("NavigationMeshInstance");
-}
-
-void NavigationMeshEditorPlugin::make_visible(bool p_visible) {
-
- if (p_visible) {
- navigation_mesh_editor->show();
- navigation_mesh_editor->bake_hbox->show();
- } else {
-
- navigation_mesh_editor->hide();
- navigation_mesh_editor->bake_hbox->hide();
- navigation_mesh_editor->edit(NULL);
- }
-}
-
-NavigationMeshEditorPlugin::NavigationMeshEditorPlugin(EditorNode *p_node) {
-
- editor = p_node;
- navigation_mesh_editor = memnew(NavigationMeshEditor);
- editor->get_viewport()->add_child(navigation_mesh_editor);
- add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, navigation_mesh_editor->bake_hbox);
- navigation_mesh_editor->hide();
- navigation_mesh_editor->bake_hbox->hide();
-}
-
-NavigationMeshEditorPlugin::~NavigationMeshEditorPlugin() {
-}
-
-#endif // RECAST_ENABLED
diff --git a/editor/plugins/navigation_mesh_editor_plugin.h b/editor/plugins/navigation_mesh_editor_plugin.h
deleted file mode 100644
index 9382467d85..0000000000
--- a/editor/plugins/navigation_mesh_editor_plugin.h
+++ /dev/null
@@ -1,87 +0,0 @@
-/*************************************************************************/
-/* navigation_mesh_editor_plugin.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifndef NAVIGATION_MESH_GENERATOR_PLUGIN_H
-#define NAVIGATION_MESH_GENERATOR_PLUGIN_H
-
-#ifdef RECAST_ENABLED
-
-#include "editor/editor_node.h"
-#include "editor/editor_plugin.h"
-#include "navigation_mesh_generator.h"
-
-class NavigationMeshEditor : public Control {
- friend class NavigationMeshEditorPlugin;
-
- GDCLASS(NavigationMeshEditor, Control);
-
- AcceptDialog *err_dialog;
-
- HBoxContainer *bake_hbox;
- Button *button_bake;
- Button *button_reset;
- Label *bake_info;
-
- NavigationMeshInstance *node;
-
- void _bake_pressed();
- void _clear_pressed();
-
-protected:
- void _node_removed(Node *p_node);
- static void _bind_methods();
- void _notification(int p_option);
-
-public:
- void edit(NavigationMeshInstance *p_nav_mesh_instance);
- NavigationMeshEditor();
- ~NavigationMeshEditor();
-};
-
-class NavigationMeshEditorPlugin : public EditorPlugin {
-
- GDCLASS(NavigationMeshEditorPlugin, EditorPlugin);
-
- NavigationMeshEditor *navigation_mesh_editor;
- EditorNode *editor;
-
-public:
- virtual String get_name() const { return "NavigationMesh"; }
- bool has_main_screen() const { return false; }
- virtual void edit(Object *p_object);
- virtual bool handles(Object *p_object) const;
- virtual void make_visible(bool p_visible);
-
- NavigationMeshEditorPlugin(EditorNode *p_node);
- ~NavigationMeshEditorPlugin();
-};
-
-#endif // RECAST_ENABLED
-#endif // NAVIGATION_MESH_GENERATOR_PLUGIN_H
diff --git a/editor/plugins/navigation_mesh_generator.cpp b/editor/plugins/navigation_mesh_generator.cpp
deleted file mode 100644
index 0537c5c31f..0000000000
--- a/editor/plugins/navigation_mesh_generator.cpp
+++ /dev/null
@@ -1,308 +0,0 @@
-/*************************************************************************/
-/* navigation_mesh_generator.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include "navigation_mesh_generator.h"
-
-#ifdef RECAST_ENABLED
-
-void NavigationMeshGenerator::_add_vertex(const Vector3 &p_vec3, Vector<float> &p_verticies) {
- p_verticies.push_back(p_vec3.x);
- p_verticies.push_back(p_vec3.y);
- p_verticies.push_back(p_vec3.z);
-}
-
-void NavigationMeshGenerator::_add_mesh(const Ref<Mesh> &p_mesh, const Transform &p_xform, Vector<float> &p_verticies, Vector<int> &p_indices) {
- int current_vertex_count = 0;
-
- for (int i = 0; i < p_mesh->get_surface_count(); i++) {
- current_vertex_count = p_verticies.size() / 3;
-
- if (p_mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES)
- continue;
-
- int index_count = 0;
- if (p_mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {
- index_count = p_mesh->surface_get_array_index_len(i);
- } else {
- index_count = p_mesh->surface_get_array_len(i);
- }
-
- ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0));
-
- int face_count = index_count / 3;
-
- Array a = p_mesh->surface_get_arrays(i);
-
- PoolVector<Vector3> mesh_vertices = a[Mesh::ARRAY_VERTEX];
- PoolVector<Vector3>::Read vr = mesh_vertices.read();
-
- if (p_mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {
-
- PoolVector<int> mesh_indices = a[Mesh::ARRAY_INDEX];
- PoolVector<int>::Read ir = mesh_indices.read();
-
- for (int i = 0; i < mesh_vertices.size(); i++) {
- _add_vertex(p_xform.xform(vr[i]), p_verticies);
- }
-
- for (int i = 0; i < face_count; i++) {
- // CCW
- p_indices.push_back(current_vertex_count + (ir[i * 3 + 0]));
- p_indices.push_back(current_vertex_count + (ir[i * 3 + 2]));
- p_indices.push_back(current_vertex_count + (ir[i * 3 + 1]));
- }
- } else {
- face_count = mesh_vertices.size() / 3;
- for (int i = 0; i < face_count; i++) {
- _add_vertex(p_xform.xform(vr[i * 3 + 0]), p_verticies);
- _add_vertex(p_xform.xform(vr[i * 3 + 2]), p_verticies);
- _add_vertex(p_xform.xform(vr[i * 3 + 1]), p_verticies);
-
- p_indices.push_back(current_vertex_count + (i * 3 + 0));
- p_indices.push_back(current_vertex_count + (i * 3 + 1));
- p_indices.push_back(current_vertex_count + (i * 3 + 2));
- }
- }
- }
-}
-
-void NavigationMeshGenerator::_parse_geometry(const Transform &p_base_inverse, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices) {
-
- if (Object::cast_to<MeshInstance>(p_node)) {
-
- MeshInstance *mesh_instance = Object::cast_to<MeshInstance>(p_node);
- Ref<Mesh> mesh = mesh_instance->get_mesh();
- if (mesh.is_valid()) {
- _add_mesh(mesh, p_base_inverse * mesh_instance->get_global_transform(), p_verticies, p_indices);
- }
- }
-
- for (int i = 0; i < p_node->get_child_count(); i++) {
- _parse_geometry(p_base_inverse, p_node->get_child(i), p_verticies, p_indices);
- }
-}
-
-void NavigationMeshGenerator::_convert_detail_mesh_to_native_navigation_mesh(const rcPolyMeshDetail *p_detail_mesh, Ref<NavigationMesh> p_nav_mesh) {
-
- PoolVector<Vector3> nav_vertices;
-
- for (int i = 0; i < p_detail_mesh->nverts; i++) {
- const float *v = &p_detail_mesh->verts[i * 3];
- nav_vertices.append(Vector3(v[0], v[1], v[2]));
- }
- p_nav_mesh->set_vertices(nav_vertices);
-
- for (int i = 0; i < p_detail_mesh->nmeshes; i++) {
- const unsigned int *m = &p_detail_mesh->meshes[i * 4];
- const unsigned int bverts = m[0];
- const unsigned int btris = m[2];
- const unsigned int ntris = m[3];
- const unsigned char *tris = &p_detail_mesh->tris[btris * 4];
- for (unsigned int j = 0; j < ntris; j++) {
- Vector<int> nav_indices;
- nav_indices.resize(3);
- nav_indices[0] = ((int)(bverts + tris[j * 4 + 0]));
- nav_indices[1] = ((int)(bverts + tris[j * 4 + 1]));
- nav_indices[2] = ((int)(bverts + tris[j * 4 + 2]));
- p_nav_mesh->add_polygon(nav_indices);
- }
- }
-}
-
-void NavigationMeshGenerator::_build_recast_navigation_mesh(Ref<NavigationMesh> p_nav_mesh, EditorProgress *ep,
- rcHeightfield *hf, rcCompactHeightfield *chf, rcContourSet *cset, rcPolyMesh *poly_mesh, rcPolyMeshDetail *detail_mesh,
- Vector<float> &vertices, Vector<int> &indices) {
- rcContext ctx;
- ep->step(TTR("Setting up Configuration..."), 1);
-
- const float *verts = vertices.ptr();
- const int nverts = vertices.size() / 3;
- const int *tris = indices.ptr();
- const int ntris = indices.size() / 3;
-
- float bmin[3], bmax[3];
- rcCalcBounds(verts, nverts, bmin, bmax);
-
- rcConfig cfg;
- memset(&cfg, 0, sizeof(cfg));
-
- cfg.cs = p_nav_mesh->get_cell_size();
- cfg.ch = p_nav_mesh->get_cell_height();
- cfg.walkableSlopeAngle = p_nav_mesh->get_agent_max_slope();
- cfg.walkableHeight = (int)Math::ceil(p_nav_mesh->get_agent_height() / cfg.ch);
- cfg.walkableClimb = (int)Math::floor(p_nav_mesh->get_agent_max_climb() / cfg.ch);
- cfg.walkableRadius = (int)Math::ceil(p_nav_mesh->get_agent_radius() / cfg.cs);
- cfg.maxEdgeLen = (int)(p_nav_mesh->get_edge_max_length() / p_nav_mesh->get_cell_size());
- cfg.maxSimplificationError = p_nav_mesh->get_edge_max_error();
- cfg.minRegionArea = (int)(p_nav_mesh->get_region_min_size() * p_nav_mesh->get_region_min_size());
- cfg.mergeRegionArea = (int)(p_nav_mesh->get_region_merge_size() * p_nav_mesh->get_region_merge_size());
- cfg.maxVertsPerPoly = (int)p_nav_mesh->get_verts_per_poly();
- cfg.detailSampleDist = p_nav_mesh->get_detail_sample_distance() < 0.9f ? 0 : p_nav_mesh->get_cell_size() * p_nav_mesh->get_detail_sample_distance();
- cfg.detailSampleMaxError = p_nav_mesh->get_cell_height() * p_nav_mesh->get_detail_sample_max_error();
-
- cfg.bmin[0] = bmin[0];
- cfg.bmin[1] = bmin[1];
- cfg.bmin[2] = bmin[2];
- cfg.bmax[0] = bmax[0];
- cfg.bmax[1] = bmax[1];
- cfg.bmax[2] = bmax[2];
-
- ep->step(TTR("Calculating grid size..."), 2);
- rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height);
-
- ep->step(TTR("Creating heightfield..."), 3);
- hf = rcAllocHeightfield();
-
- ERR_FAIL_COND(!hf);
- ERR_FAIL_COND(!rcCreateHeightfield(&ctx, *hf, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch));
-
- ep->step(TTR("Marking walkable triangles..."), 4);
- {
- Vector<unsigned char> tri_areas;
- tri_areas.resize(ntris);
-
- ERR_FAIL_COND(tri_areas.size() == 0);
-
- memset(tri_areas.ptrw(), 0, ntris * sizeof(unsigned char));
- rcMarkWalkableTriangles(&ctx, cfg.walkableSlopeAngle, verts, nverts, tris, ntris, tri_areas.ptrw());
-
- ERR_FAIL_COND(!rcRasterizeTriangles(&ctx, verts, nverts, tris, tri_areas.ptr(), ntris, *hf, cfg.walkableClimb));
- }
-
- if (p_nav_mesh->get_filter_low_hanging_obstacles())
- rcFilterLowHangingWalkableObstacles(&ctx, cfg.walkableClimb, *hf);
- if (p_nav_mesh->get_filter_ledge_spans())
- rcFilterLedgeSpans(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf);
- if (p_nav_mesh->get_filter_walkable_low_height_spans())
- rcFilterWalkableLowHeightSpans(&ctx, cfg.walkableHeight, *hf);
-
- ep->step(TTR("Constructing compact heightfield..."), 5);
-
- chf = rcAllocCompactHeightfield();
-
- ERR_FAIL_COND(!chf);
- ERR_FAIL_COND(!rcBuildCompactHeightfield(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf, *chf));
-
- rcFreeHeightField(hf);
- hf = 0;
-
- ep->step(TTR("Eroding walkable area..."), 6);
- ERR_FAIL_COND(!rcErodeWalkableArea(&ctx, cfg.walkableRadius, *chf));
-
- ep->step(TTR("Partitioning..."), 7);
- if (p_nav_mesh->get_sample_partition_type() == NavigationMesh::SAMPLE_PARTITION_WATERSHED) {
- ERR_FAIL_COND(!rcBuildDistanceField(&ctx, *chf));
- ERR_FAIL_COND(!rcBuildRegions(&ctx, *chf, 0, cfg.minRegionArea, cfg.mergeRegionArea));
- } else if (p_nav_mesh->get_sample_partition_type() == NavigationMesh::SAMPLE_PARTITION_MONOTONE) {
- ERR_FAIL_COND(!rcBuildRegionsMonotone(&ctx, *chf, 0, cfg.minRegionArea, cfg.mergeRegionArea));
- } else {
- ERR_FAIL_COND(!rcBuildLayerRegions(&ctx, *chf, 0, cfg.minRegionArea));
- }
-
- ep->step(TTR("Creating contours..."), 8);
-
- cset = rcAllocContourSet();
-
- ERR_FAIL_COND(!cset);
- ERR_FAIL_COND(!rcBuildContours(&ctx, *chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cset));
-
- ep->step(TTR("Creating polymesh..."), 9);
-
- poly_mesh = rcAllocPolyMesh();
- ERR_FAIL_COND(!poly_mesh);
- ERR_FAIL_COND(!rcBuildPolyMesh(&ctx, *cset, cfg.maxVertsPerPoly, *poly_mesh));
-
- detail_mesh = rcAllocPolyMeshDetail();
- ERR_FAIL_COND(!detail_mesh);
- ERR_FAIL_COND(!rcBuildPolyMeshDetail(&ctx, *poly_mesh, *chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *detail_mesh));
-
- rcFreeCompactHeightfield(chf);
- chf = 0;
- rcFreeContourSet(cset);
- cset = 0;
-
- ep->step(TTR("Converting to native navigation mesh..."), 10);
-
- _convert_detail_mesh_to_native_navigation_mesh(detail_mesh, p_nav_mesh);
-
- rcFreePolyMesh(poly_mesh);
- poly_mesh = 0;
- rcFreePolyMeshDetail(detail_mesh);
- detail_mesh = 0;
-}
-
-void NavigationMeshGenerator::bake(Ref<NavigationMesh> p_nav_mesh, Node *p_node) {
-
- ERR_FAIL_COND(!p_nav_mesh.is_valid());
-
- EditorProgress ep("bake", TTR("Navigation Mesh Generator Setup:"), 11);
- ep.step(TTR("Parsing Geometry..."), 0);
-
- Vector<float> vertices;
- Vector<int> indices;
-
- _parse_geometry(Object::cast_to<Spatial>(p_node)->get_global_transform().affine_inverse(), p_node, vertices, indices);
-
- if (vertices.size() > 0 && indices.size() > 0) {
-
- rcHeightfield *hf = NULL;
- rcCompactHeightfield *chf = NULL;
- rcContourSet *cset = NULL;
- rcPolyMesh *poly_mesh = NULL;
- rcPolyMeshDetail *detail_mesh = NULL;
-
- _build_recast_navigation_mesh(p_nav_mesh, &ep, hf, chf, cset, poly_mesh, detail_mesh, vertices, indices);
-
- rcFreeHeightField(hf);
- hf = 0;
-
- rcFreeCompactHeightfield(chf);
- chf = 0;
-
- rcFreeContourSet(cset);
- cset = 0;
-
- rcFreePolyMesh(poly_mesh);
- poly_mesh = 0;
-
- rcFreePolyMeshDetail(detail_mesh);
- detail_mesh = 0;
- }
- ep.step(TTR("Done!"), 11);
-}
-
-void NavigationMeshGenerator::clear(Ref<NavigationMesh> p_nav_mesh) {
- if (p_nav_mesh.is_valid()) {
- p_nav_mesh->clear_polygons();
- p_nav_mesh->set_vertices(PoolVector<Vector3>());
- }
-}
-
-#endif //RECAST_ENABLED
diff --git a/editor/plugins/navigation_mesh_generator.h b/editor/plugins/navigation_mesh_generator.h
deleted file mode 100644
index d26f541b8d..0000000000
--- a/editor/plugins/navigation_mesh_generator.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*************************************************************************/
-/* navigation_mesh_generator.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifndef NAVIGATION_MESH_GENERATOR_H
-#define NAVIGATION_MESH_GENERATOR_H
-
-#ifdef RECAST_ENABLED
-
-#include "editor/editor_node.h"
-#include "editor/editor_settings.h"
-
-#include "scene/3d/mesh_instance.h"
-
-#include "scene/3d/navigation_mesh.h"
-
-#include "os/thread.h"
-#include "scene/resources/shape.h"
-
-#include <Recast.h>
-
-class NavigationMeshGenerator {
-protected:
- static void _add_vertex(const Vector3 &p_vec3, Vector<float> &p_verticies);
- static void _add_mesh(const Ref<Mesh> &p_mesh, const Transform &p_xform, Vector<float> &p_verticies, Vector<int> &p_indices);
- static void _parse_geometry(const Transform &p_base_inverse, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices);
-
- static void _convert_detail_mesh_to_native_navigation_mesh(const rcPolyMeshDetail *p_detail_mesh, Ref<NavigationMesh> p_nav_mesh);
- static void _build_recast_navigation_mesh(Ref<NavigationMesh> p_nav_mesh, EditorProgress *ep,
- rcHeightfield *hf, rcCompactHeightfield *chf, rcContourSet *cset, rcPolyMesh *poly_mesh,
- rcPolyMeshDetail *detail_mesh, Vector<float> &vertices, Vector<int> &indices);
-
-public:
- static void bake(Ref<NavigationMesh> p_nav_mesh, Node *p_node);
- static void clear(Ref<NavigationMesh> p_nav_mesh);
-};
-
-#endif // RECAST_ENABLED
-
-#endif // NAVIGATION_MESH_GENERATOR_H
diff --git a/editor/plugins/root_motion_editor_plugin.cpp b/editor/plugins/root_motion_editor_plugin.cpp
new file mode 100644
index 0000000000..89c1b3a978
--- /dev/null
+++ b/editor/plugins/root_motion_editor_plugin.cpp
@@ -0,0 +1,293 @@
+#include "root_motion_editor_plugin.h"
+#include "editor/editor_node.h"
+#include "scene/main/viewport.h"
+
+void EditorPropertyRootMotion::_confirmed() {
+
+ TreeItem *ti = filters->get_selected();
+ if (!ti)
+ return;
+
+ NodePath path = ti->get_metadata(0);
+ emit_signal("property_changed", get_edited_property(), path);
+ update_property();
+ filter_dialog->hide(); //may come from activated
+}
+
+void EditorPropertyRootMotion::_node_assign() {
+
+ NodePath current = get_edited_object()->get(get_edited_property());
+
+ AnimationTree *atree = Object::cast_to<AnimationTree>(get_edited_object());
+ if (!atree->has_node(atree->get_animation_player())) {
+ EditorNode::get_singleton()->show_warning(TTR("AnimationTree has no path set to an AnimationPlayer"));
+ return;
+ }
+ AnimationPlayer *player = Object::cast_to<AnimationPlayer>(atree->get_node(atree->get_animation_player()));
+ if (!player) {
+ EditorNode::get_singleton()->show_warning(TTR("Path to AnimationPlayer is invalid"));
+ return;
+ }
+
+ Node *base = player->get_node(player->get_root());
+
+ if (!base) {
+ EditorNode::get_singleton()->show_warning(TTR("Animation player has no valid root node path, so unable to retrieve track names."));
+ return;
+ }
+
+ Set<String> paths;
+ {
+ List<StringName> animations;
+ player->get_animation_list(&animations);
+
+ for (List<StringName>::Element *E = animations.front(); E; E = E->next()) {
+
+ Ref<Animation> anim = player->get_animation(E->get());
+ for (int i = 0; i < anim->get_track_count(); i++) {
+ paths.insert(anim->track_get_path(i));
+ }
+ }
+ }
+
+ filters->clear();
+ TreeItem *root = filters->create_item();
+
+ Map<String, TreeItem *> parenthood;
+
+ for (Set<String>::Element *E = paths.front(); E; E = E->next()) {
+
+ NodePath path = E->get();
+ TreeItem *ti = NULL;
+ String accum;
+ for (int i = 0; i < path.get_name_count(); i++) {
+ String name = path.get_name(i);
+ if (accum != String()) {
+ accum += "/";
+ }
+ accum += name;
+ if (!parenthood.has(accum)) {
+ if (ti) {
+ ti = filters->create_item(ti);
+ } else {
+ ti = filters->create_item(root);
+ }
+ parenthood[accum] = ti;
+ ti->set_text(0, name);
+ ti->set_selectable(0, false);
+ ti->set_editable(0, false);
+
+ if (base->has_node(accum)) {
+ Node *node = base->get_node(accum);
+ if (has_icon(node->get_class(), "EditorIcons")) {
+ ti->set_icon(0, get_icon(node->get_class(), "EditorIcons"));
+ } else {
+ ti->set_icon(0, get_icon("Node", "EditorIcons"));
+ }
+ }
+
+ } else {
+ ti = parenthood[accum];
+ }
+ }
+
+ Node *node = NULL;
+ if (base->has_node(accum)) {
+ node = base->get_node(accum);
+ }
+ if (!node)
+ continue; //no node, cant edit
+
+ if (path.get_subname_count()) {
+
+ String concat = path.get_concatenated_subnames();
+
+ Skeleton *skeleton = Object::cast_to<Skeleton>(node);
+ if (skeleton && skeleton->find_bone(concat) != -1) {
+ //path in skeleton
+ String bone = concat;
+ int idx = skeleton->find_bone(bone);
+ List<String> bone_path;
+ while (idx != -1) {
+ bone_path.push_front(skeleton->get_bone_name(idx));
+ idx = skeleton->get_bone_parent(idx);
+ }
+
+ accum += ":";
+ for (List<String>::Element *F = bone_path.front(); F; F = F->next()) {
+ if (F != bone_path.front()) {
+ accum += "/";
+ }
+
+ accum += F->get();
+ if (!parenthood.has(accum)) {
+ ti = filters->create_item(ti);
+ parenthood[accum] = ti;
+ ti->set_text(0, F->get());
+ ti->set_selectable(0, true);
+ ti->set_editable(0, false);
+ ti->set_icon(0, get_icon("BoneAttachment", "EditorIcons"));
+ ti->set_metadata(0, accum);
+ } else {
+ ti = parenthood[accum];
+ }
+ }
+
+ ti->set_selectable(0, true);
+ ti->set_text(0, concat);
+ ti->set_icon(0, get_icon("BoneAttachment", "EditorIcons"));
+ ti->set_metadata(0, path);
+ if (path == current) {
+ ti->select(0);
+ }
+
+ } else {
+ //just a property
+ ti = filters->create_item(ti);
+ ti->set_text(0, concat);
+ ti->set_selectable(0, true);
+ ti->set_metadata(0, path);
+ if (path == current) {
+ ti->select(0);
+ }
+ }
+ } else {
+ if (ti) {
+ //just a node, likely call or animation track
+ ti->set_selectable(0, true);
+ ti->set_metadata(0, path);
+ if (path == current) {
+ ti->select(0);
+ }
+ }
+ }
+ }
+
+ filters->ensure_cursor_is_visible();
+ filter_dialog->popup_centered_ratio();
+}
+
+void EditorPropertyRootMotion::_node_clear() {
+
+ emit_signal("property_changed", get_edited_property(), NodePath());
+ update_property();
+}
+
+void EditorPropertyRootMotion::update_property() {
+
+ NodePath p = get_edited_object()->get(get_edited_property());
+
+ assign->set_tooltip(p);
+ if (p == NodePath()) {
+ assign->set_icon(Ref<Texture>());
+ assign->set_text(TTR("Assign.."));
+ assign->set_flat(false);
+ return;
+ }
+ assign->set_flat(true);
+
+ Node *base_node = NULL;
+ if (base_hint != NodePath()) {
+ if (get_tree()->get_root()->has_node(base_hint)) {
+ base_node = get_tree()->get_root()->get_node(base_hint);
+ }
+ } else {
+ base_node = Object::cast_to<Node>(get_edited_object());
+ }
+
+ if (!base_node || !base_node->has_node(p)) {
+ assign->set_icon(Ref<Texture>());
+ assign->set_text(p);
+ return;
+ }
+
+ Node *target_node = base_node->get_node(p);
+ ERR_FAIL_COND(!target_node);
+
+ assign->set_text(target_node->get_name());
+
+ Ref<Texture> icon;
+ if (has_icon(target_node->get_class(), "EditorIcons"))
+ icon = get_icon(target_node->get_class(), "EditorIcons");
+ else
+ icon = get_icon("Node", "EditorIcons");
+
+ assign->set_icon(icon);
+}
+
+void EditorPropertyRootMotion::setup(const NodePath &p_base_hint) {
+
+ base_hint = p_base_hint;
+}
+
+void EditorPropertyRootMotion::_notification(int p_what) {
+
+ if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
+ Ref<Texture> t = get_icon("Clear", "EditorIcons");
+ clear->set_icon(t);
+ }
+}
+
+void EditorPropertyRootMotion::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("_confirmed"), &EditorPropertyRootMotion::_confirmed);
+ ClassDB::bind_method(D_METHOD("_node_assign"), &EditorPropertyRootMotion::_node_assign);
+ ClassDB::bind_method(D_METHOD("_node_clear"), &EditorPropertyRootMotion::_node_clear);
+}
+
+EditorPropertyRootMotion::EditorPropertyRootMotion() {
+
+ HBoxContainer *hbc = memnew(HBoxContainer);
+ add_child(hbc);
+ assign = memnew(Button);
+ assign->set_flat(true);
+ assign->set_h_size_flags(SIZE_EXPAND_FILL);
+ assign->set_clip_text(true);
+ assign->connect("pressed", this, "_node_assign");
+ hbc->add_child(assign);
+
+ clear = memnew(Button);
+ clear->set_flat(true);
+ clear->connect("pressed", this, "_node_clear");
+ hbc->add_child(clear);
+
+ filter_dialog = memnew(ConfirmationDialog);
+ add_child(filter_dialog);
+ filter_dialog->set_title(TTR("Edit Filtered Tracks:"));
+ filter_dialog->connect("confirmed", this, "_confirmed");
+
+ filters = memnew(Tree);
+ filter_dialog->add_child(filters);
+ filters->set_v_size_flags(SIZE_EXPAND_FILL);
+ filters->set_hide_root(true);
+ filters->connect("item_activated", this, "_confirmed");
+ //filters->connect("item_edited", this, "_filter_edited");
+}
+//////////////////////////
+
+bool EditorInspectorRootMotionPlugin::can_handle(Object *p_object) {
+ return true; //can handle everything
+}
+
+void EditorInspectorRootMotionPlugin::parse_begin(Object *p_object) {
+ //do none
+}
+
+bool EditorInspectorRootMotionPlugin::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage) {
+
+ if (p_path == "root_motion_track" && p_object->is_class("AnimationTree") && p_type == Variant::NODE_PATH) {
+ print_line("use custom!");
+ EditorPropertyRootMotion *editor = memnew(EditorPropertyRootMotion);
+ if (p_hint == PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE && p_hint_text != String()) {
+ editor->setup(p_hint_text);
+ }
+ add_property_editor(p_path, editor);
+ return true;
+ }
+
+ return false; //can be overriden, although it will most likely be last anyway
+}
+
+void EditorInspectorRootMotionPlugin::parse_end() {
+ //do none
+}
diff --git a/editor/plugins/root_motion_editor_plugin.h b/editor/plugins/root_motion_editor_plugin.h
new file mode 100644
index 0000000000..84af47872f
--- /dev/null
+++ b/editor/plugins/root_motion_editor_plugin.h
@@ -0,0 +1,42 @@
+#ifndef ROOT_MOTION_EDITOR_PLUGIN_H
+#define ROOT_MOTION_EDITOR_PLUGIN_H
+
+#include "editor/editor_inspector.h"
+#include "editor/editor_spin_slider.h"
+#include "editor/property_selector.h"
+#include "scene/animation/animation_tree.h"
+
+class EditorPropertyRootMotion : public EditorProperty {
+ GDCLASS(EditorPropertyRootMotion, EditorProperty)
+ Button *assign;
+ Button *clear;
+ NodePath base_hint;
+
+ ConfirmationDialog *filter_dialog;
+ Tree *filters;
+
+ void _confirmed();
+ void _node_assign();
+ void _node_clear();
+
+protected:
+ static void _bind_methods();
+ void _notification(int p_what);
+
+public:
+ virtual void update_property();
+ void setup(const NodePath &p_base_hint);
+ EditorPropertyRootMotion();
+};
+
+class EditorInspectorRootMotionPlugin : public EditorInspectorPlugin {
+ GDCLASS(EditorInspectorRootMotionPlugin, EditorInspectorPlugin)
+
+public:
+ virtual bool can_handle(Object *p_object);
+ virtual void parse_begin(Object *p_object);
+ virtual bool parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage);
+ virtual void parse_end();
+};
+
+#endif // ROOT_MOTION_EDITOR_PLUGIN_H
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 94dcbd8e18..aa4673f41e 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -804,12 +804,12 @@ bool ScriptEditor::_test_script_times_on_disk(Ref<Script> p_for_script) {
void ScriptEditor::_file_dialog_action(String p_file) {
switch (file_dialog_option) {
- case FILE_SAVE_THEME_AS: {
+ case THEME_SAVE_AS: {
if (!EditorSettings::get_singleton()->save_text_editor_theme_as(p_file)) {
editor->show_warning(TTR("Error while saving theme"), TTR("Error saving"));
}
} break;
- case FILE_IMPORT_THEME: {
+ case THEME_IMPORT: {
if (!EditorSettings::get_singleton()->import_text_editor_theme(p_file)) {
editor->show_warning(TTR("Error importing theme"), TTR("Error importing"));
}
@@ -859,33 +859,6 @@ void ScriptEditor::_menu_option(int p_option) {
save_all_scripts();
} break;
- case FILE_IMPORT_THEME: {
- file_dialog->set_mode(EditorFileDialog::MODE_OPEN_FILE);
- file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
- file_dialog_option = FILE_IMPORT_THEME;
- file_dialog->clear_filters();
- file_dialog->add_filter("*.tet");
- file_dialog->popup_centered_ratio();
- file_dialog->set_title(TTR("Import Theme"));
- } break;
- case FILE_RELOAD_THEME: {
- EditorSettings::get_singleton()->load_text_editor_theme();
- } break;
- case FILE_SAVE_THEME: {
- if (!EditorSettings::get_singleton()->save_text_editor_theme()) {
- editor->show_warning(TTR("Error while saving theme"), TTR("Error saving"));
- }
- } break;
- case FILE_SAVE_THEME_AS: {
- file_dialog->set_mode(EditorFileDialog::MODE_SAVE_FILE);
- file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
- file_dialog_option = FILE_SAVE_THEME_AS;
- file_dialog->clear_filters();
- file_dialog->add_filter("*.tet");
- file_dialog->set_current_path(EditorSettings::get_singleton()->get_text_editor_themes_dir().plus_file(EditorSettings::get_singleton()->get("text_editor/theme/color_theme")));
- file_dialog->popup_centered_ratio();
- file_dialog->set_title(TTR("Save Theme As..."));
- } break;
case SEARCH_HELP: {
help_search_dialog->popup();
@@ -1143,6 +1116,38 @@ void ScriptEditor::_menu_option(int p_option) {
}
}
+void ScriptEditor::_theme_option(int p_option) {
+ switch (p_option) {
+ case THEME_IMPORT: {
+ file_dialog->set_mode(EditorFileDialog::MODE_OPEN_FILE);
+ file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
+ file_dialog_option = THEME_IMPORT;
+ file_dialog->clear_filters();
+ file_dialog->add_filter("*.tet");
+ file_dialog->popup_centered_ratio();
+ file_dialog->set_title(TTR("Import Theme"));
+ } break;
+ case THEME_RELOAD: {
+ EditorSettings::get_singleton()->load_text_editor_theme();
+ } break;
+ case THEME_SAVE: {
+ if (!EditorSettings::get_singleton()->save_text_editor_theme()) {
+ editor->show_warning(TTR("Error while saving theme"), TTR("Error saving"));
+ }
+ } break;
+ case THEME_SAVE_AS: {
+ file_dialog->set_mode(EditorFileDialog::MODE_SAVE_FILE);
+ file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
+ file_dialog_option = THEME_SAVE_AS;
+ file_dialog->clear_filters();
+ file_dialog->add_filter("*.tet");
+ file_dialog->set_current_path(EditorSettings::get_singleton()->get_text_editor_themes_dir().plus_file(EditorSettings::get_singleton()->get("text_editor/theme/color_theme")));
+ file_dialog->popup_centered_ratio();
+ file_dialog->set_title(TTR("Save Theme As..."));
+ } break;
+ }
+}
+
void ScriptEditor::_tab_changed(int p_which) {
ensure_select_current();
@@ -1215,6 +1220,9 @@ void ScriptEditor::_notification(int p_what) {
script_forward->set_icon(get_icon("Forward", "EditorIcons"));
script_back->set_icon(get_icon("Back", "EditorIcons"));
+ members_overview_alphabeta_sort_button->set_icon(get_icon("Sort", "EditorIcons"));
+ filename->add_style_override("normal", editor->get_gui_base()->get_stylebox("normal", "LineEdit"));
+
recent_scripts->set_as_minsize();
} break;
@@ -1404,17 +1412,20 @@ void ScriptEditor::_update_members_overview_visibility() {
ScriptEditorBase *se = _get_current_editor();
if (!se) {
- members_overview_buttons_hbox->set_visible(false);
+ members_overview_alphabeta_sort_button->set_visible(false);
members_overview->set_visible(false);
+ overview_vbox->set_visible(false);
return;
}
if (members_overview_enabled && se->show_members_overview()) {
- members_overview_buttons_hbox->set_visible(true);
+ members_overview_alphabeta_sort_button->set_visible(true);
members_overview->set_visible(true);
+ overview_vbox->set_visible(true);
} else {
- members_overview_buttons_hbox->set_visible(false);
+ members_overview_alphabeta_sort_button->set_visible(false);
members_overview->set_visible(false);
+ overview_vbox->set_visible(false);
}
}
@@ -1440,6 +1451,11 @@ void ScriptEditor::_update_members_overview() {
members_overview->add_item(functions[i].get_slice(":", 0));
members_overview->set_item_metadata(i, functions[i].get_slice(":", 1).to_int() - 1);
}
+
+ String path = se->get_edited_script()->get_path();
+ bool built_in = !path.is_resource_file();
+ String name = built_in ? path.get_file() : se->get_name();
+ filename->set_text(name);
}
void ScriptEditor::_update_help_overview_visibility() {
@@ -1458,10 +1474,13 @@ void ScriptEditor::_update_help_overview_visibility() {
}
if (help_overview_enabled) {
- members_overview_buttons_hbox->set_visible(false);
+ members_overview_alphabeta_sort_button->set_visible(false);
help_overview->set_visible(true);
+ overview_vbox->set_visible(true);
+ filename->set_text(se->get_name());
} else {
help_overview->set_visible(false);
+ overview_vbox->set_visible(false);
}
}
@@ -2577,6 +2596,7 @@ void ScriptEditor::_bind_methods() {
ClassDB::bind_method("_close_all_tabs", &ScriptEditor::_close_all_tabs);
ClassDB::bind_method("_close_other_tabs", &ScriptEditor::_close_other_tabs);
ClassDB::bind_method("_open_recent_script", &ScriptEditor::_open_recent_script);
+ ClassDB::bind_method("_theme_option", &ScriptEditor::_theme_option);
ClassDB::bind_method("_editor_play", &ScriptEditor::_editor_play);
ClassDB::bind_method("_editor_pause", &ScriptEditor::_editor_pause);
ClassDB::bind_method("_editor_stop", &ScriptEditor::_editor_stop);
@@ -2673,13 +2693,19 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) {
add_child(context_menu);
context_menu->connect("id_pressed", this, "_menu_option");
- members_overview_vbox = memnew(VBoxContainer);
- members_overview_vbox->set_custom_minimum_size(Size2(0, 90));
- members_overview_vbox->set_v_size_flags(SIZE_EXPAND_FILL);
+ overview_vbox = memnew(VBoxContainer);
+ overview_vbox->set_custom_minimum_size(Size2(0, 90));
+ overview_vbox->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ list_split->add_child(overview_vbox);
+ buttons_hbox = memnew(HBoxContainer);
+ overview_vbox->add_child(buttons_hbox);
- list_split->add_child(members_overview_vbox);
- members_overview_buttons_hbox = memnew(HBoxContainer);
- members_overview_vbox->add_child(members_overview_buttons_hbox);
+ filename = memnew(Label);
+ filename->set_clip_text(true);
+ filename->set_h_size_flags(SIZE_EXPAND_FILL);
+ filename->add_style_override("normal", EditorNode::get_singleton()->get_gui_base()->get_stylebox("normal", "LineEdit"));
+ buttons_hbox->add_child(filename);
members_overview_alphabeta_sort_button = memnew(ToolButton);
members_overview_alphabeta_sort_button->set_tooltip(TTR("Toggle alphabetical sorting of the method list."));
@@ -2687,10 +2713,10 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) {
members_overview_alphabeta_sort_button->set_pressed(EditorSettings::get_singleton()->get("text_editor/tools/sort_members_outline_alphabetically"));
members_overview_alphabeta_sort_button->connect("toggled", this, "_toggle_members_overview_alpha_sort");
- members_overview_buttons_hbox->add_child(members_overview_alphabeta_sort_button);
+ buttons_hbox->add_child(members_overview_alphabeta_sort_button);
members_overview = memnew(ItemList);
- members_overview_vbox->add_child(members_overview);
+ overview_vbox->add_child(members_overview);
members_overview->set_allow_reselect(true);
members_overview->set_custom_minimum_size(Size2(0, 90)); //need to give a bit of limit to avoid it from disappearing
@@ -2699,7 +2725,7 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) {
members_overview->set_drag_forwarding(this);
help_overview = memnew(ItemList);
- members_overview_vbox->add_child(help_overview);
+ overview_vbox->add_child(help_overview);
help_overview->set_allow_reselect(true);
help_overview->set_custom_minimum_size(Size2(0, 90)); //need to give a bit of limit to avoid it from disappearing
help_overview->set_v_size_flags(SIZE_EXPAND_FILL);
@@ -2743,10 +2769,18 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) {
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/history_previous", TTR("History Prev"), KEY_MASK_ALT | KEY_LEFT), WINDOW_PREV);
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/history_next", TTR("History Next"), KEY_MASK_ALT | KEY_RIGHT), WINDOW_NEXT);
file_menu->get_popup()->add_separator();
- file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/import_theme", TTR("Import Theme")), FILE_IMPORT_THEME);
- file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/reload_theme", TTR("Reload Theme")), FILE_RELOAD_THEME);
- file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save_theme", TTR("Save Theme")), FILE_SAVE_THEME);
- file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save_theme_as", TTR("Save Theme As")), FILE_SAVE_THEME_AS);
+
+ file_menu->get_popup()->add_submenu_item(TTR("Theme"), "Theme", FILE_THEME);
+
+ theme_submenu = memnew(PopupMenu);
+ theme_submenu->set_name("Theme");
+ file_menu->get_popup()->add_child(theme_submenu);
+ theme_submenu->connect("id_pressed", this, "_theme_option");
+ theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/import_theme", TTR("Import Theme")), THEME_IMPORT);
+ theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/reload_theme", TTR("Reload Theme")), THEME_RELOAD);
+ theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/save_theme", TTR("Save Theme")), THEME_SAVE);
+ theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/save_theme_as", TTR("Save Theme As")), THEME_SAVE_AS);
+
file_menu->get_popup()->add_separator();
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_docs", TTR("Close Docs")), CLOSE_DOCS);
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_file", TTR("Close"), KEY_MASK_CMD | KEY_W), FILE_CLOSE);
@@ -2850,7 +2884,7 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) {
error_dialog = memnew(AcceptDialog);
add_child(error_dialog);
- error_dialog->get_ok()->set_text(TTR("I see.."));
+ error_dialog->get_ok()->set_text(TTR("I see..."));
debugger = memnew(ScriptEditorDebugger(editor));
debugger->connect("goto_script_line", this, "_goto_script_line");
diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h
index a2ff47cd99..67f506fdda 100644
--- a/editor/plugins/script_editor_plugin.h
+++ b/editor/plugins/script_editor_plugin.h
@@ -134,10 +134,7 @@ class ScriptEditor : public PanelContainer {
FILE_SAVE,
FILE_SAVE_AS,
FILE_SAVE_ALL,
- FILE_IMPORT_THEME,
- FILE_RELOAD_THEME,
- FILE_SAVE_THEME,
- FILE_SAVE_THEME_AS,
+ FILE_THEME,
FILE_RUN,
FILE_CLOSE,
CLOSE_DOCS,
@@ -168,6 +165,13 @@ class ScriptEditor : public PanelContainer {
WINDOW_SELECT_BASE = 100
};
+ enum {
+ THEME_IMPORT,
+ THEME_RELOAD,
+ THEME_SAVE,
+ THEME_SAVE_AS
+ };
+
enum ScriptSortBy {
SORT_BY_NAME,
SORT_BY_PATH,
@@ -190,6 +194,7 @@ class ScriptEditor : public PanelContainer {
uint64_t idle;
PopupMenu *recent_scripts;
+ PopupMenu *theme_submenu;
Button *help_search;
Button *site_search;
@@ -199,8 +204,9 @@ class ScriptEditor : public PanelContainer {
ItemList *script_list;
HSplitContainer *script_split;
ItemList *members_overview;
- VBoxContainer *members_overview_vbox;
- HBoxContainer *members_overview_buttons_hbox;
+ VBoxContainer *overview_vbox;
+ HBoxContainer *buttons_hbox;
+ Label *filename;
ToolButton *members_overview_alphabeta_sort_button;
bool members_overview_enabled;
ItemList *help_overview;
@@ -250,6 +256,7 @@ class ScriptEditor : public PanelContainer {
void _tab_changed(int p_which);
void _menu_option(int p_option);
+ void _theme_option(int p_option);
Tree *disk_changed_list;
ConfirmationDialog *disk_changed;
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 45f5e667fa..aef2a53dd1 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -1727,7 +1727,7 @@ void ScriptTextEditor::register_editor() {
ED_SHORTCUT("script_text_editor/select_all", TTR("Select All"), KEY_MASK_CMD | KEY_A);
ED_SHORTCUT("script_text_editor/move_up", TTR("Move Up"), KEY_MASK_ALT | KEY_UP);
ED_SHORTCUT("script_text_editor/move_down", TTR("Move Down"), KEY_MASK_ALT | KEY_DOWN);
- ED_SHORTCUT("script_text_editor/delete_line", TTR("Delete Line"), KEY_MASK_CTRL | KEY_MASK_SHIFT | KEY_K);
+ ED_SHORTCUT("script_text_editor/delete_line", TTR("Delete Line"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_K);
//leave these at zero, same can be accomplished with tab/shift-tab, including selection
//the next/previous in history shortcut in this case makes a lot more sene.
@@ -1740,28 +1740,36 @@ void ScriptTextEditor::register_editor() {
ED_SHORTCUT("script_text_editor/unfold_all_lines", TTR("Unfold All Lines"), 0);
#ifdef OSX_ENABLED
ED_SHORTCUT("script_text_editor/clone_down", TTR("Clone Down"), KEY_MASK_SHIFT | KEY_MASK_CMD | KEY_C);
- ED_SHORTCUT("script_text_editor/complete_symbol", TTR("Complete Symbol"), KEY_MASK_CTRL | KEY_SPACE);
#else
ED_SHORTCUT("script_text_editor/clone_down", TTR("Clone Down"), KEY_MASK_CMD | KEY_B);
- ED_SHORTCUT("script_text_editor/complete_symbol", TTR("Complete Symbol"), KEY_MASK_CMD | KEY_SPACE);
#endif
- ED_SHORTCUT("script_text_editor/trim_trailing_whitespace", TTR("Trim Trailing Whitespace"), KEY_MASK_CTRL | KEY_MASK_ALT | KEY_T);
- ED_SHORTCUT("script_text_editor/convert_indent_to_spaces", TTR("Convert Indent To Spaces"), KEY_MASK_CTRL | KEY_MASK_SHIFT | KEY_Y);
- ED_SHORTCUT("script_text_editor/convert_indent_to_tabs", TTR("Convert Indent To Tabs"), KEY_MASK_CTRL | KEY_MASK_SHIFT | KEY_X);
+ ED_SHORTCUT("script_text_editor/complete_symbol", TTR("Complete Symbol"), KEY_MASK_CMD | KEY_SPACE);
+ ED_SHORTCUT("script_text_editor/trim_trailing_whitespace", TTR("Trim Trailing Whitespace"), KEY_MASK_CMD | KEY_MASK_ALT | KEY_T);
+ ED_SHORTCUT("script_text_editor/convert_indent_to_spaces", TTR("Convert Indent To Spaces"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_Y);
+ ED_SHORTCUT("script_text_editor/convert_indent_to_tabs", TTR("Convert Indent To Tabs"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_X);
ED_SHORTCUT("script_text_editor/auto_indent", TTR("Auto Indent"), KEY_MASK_CMD | KEY_I);
+#ifdef OSX_ENABLED
+ ED_SHORTCUT("script_text_editor/toggle_breakpoint", TTR("Toggle Breakpoint"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_B);
+#else
ED_SHORTCUT("script_text_editor/toggle_breakpoint", TTR("Toggle Breakpoint"), KEY_F9);
- ED_SHORTCUT("script_text_editor/remove_all_breakpoints", TTR("Remove All Breakpoints"), KEY_MASK_CTRL | KEY_MASK_SHIFT | KEY_F9);
- ED_SHORTCUT("script_text_editor/goto_next_breakpoint", TTR("Goto Next Breakpoint"), KEY_MASK_CTRL | KEY_PERIOD);
- ED_SHORTCUT("script_text_editor/goto_previous_breakpoint", TTR("Goto Previous Breakpoint"), KEY_MASK_CTRL | KEY_COMMA);
+#endif
+ ED_SHORTCUT("script_text_editor/remove_all_breakpoints", TTR("Remove All Breakpoints"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_F9);
+ ED_SHORTCUT("script_text_editor/goto_next_breakpoint", TTR("Goto Next Breakpoint"), KEY_MASK_CMD | KEY_PERIOD);
+ ED_SHORTCUT("script_text_editor/goto_previous_breakpoint", TTR("Goto Previous Breakpoint"), KEY_MASK_CMD | KEY_COMMA);
ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Convert To Uppercase"), KEY_MASK_SHIFT | KEY_F4);
ED_SHORTCUT("script_text_editor/convert_to_lowercase", TTR("Convert To Lowercase"), KEY_MASK_SHIFT | KEY_F3);
ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize"), KEY_MASK_SHIFT | KEY_F2);
ED_SHORTCUT("script_text_editor/find", TTR("Find..."), KEY_MASK_CMD | KEY_F);
+#ifdef OSX_ENABLED
+ ED_SHORTCUT("script_text_editor/find_next", TTR("Find Next"), KEY_MASK_CMD | KEY_G);
+ ED_SHORTCUT("script_text_editor/find_previous", TTR("Find Previous"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_G);
+#else
ED_SHORTCUT("script_text_editor/find_next", TTR("Find Next"), KEY_F3);
ED_SHORTCUT("script_text_editor/find_previous", TTR("Find Previous"), KEY_MASK_SHIFT | KEY_F3);
+#endif
ED_SHORTCUT("script_text_editor/replace", TTR("Replace..."), KEY_MASK_CMD | KEY_R);
ED_SHORTCUT("script_text_editor/find_in_files", TTR("Find in files..."), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_F);
@@ -1769,7 +1777,11 @@ void ScriptTextEditor::register_editor() {
ED_SHORTCUT("script_text_editor/goto_function", TTR("Goto Function..."), KEY_MASK_ALT | KEY_MASK_CMD | KEY_F);
ED_SHORTCUT("script_text_editor/goto_line", TTR("Goto Line..."), KEY_MASK_CMD | KEY_L);
+#ifdef OSX_ENABLED
+ ED_SHORTCUT("script_text_editor/contextual_help", TTR("Contextual Help"), KEY_MASK_ALT | KEY_MASK_SHIFT | KEY_SPACE);
+#else
ED_SHORTCUT("script_text_editor/contextual_help", TTR("Contextual Help"), KEY_MASK_SHIFT | KEY_F1);
+#endif
ScriptEditor::register_create_script_editor_function(create_editor);
}
diff --git a/editor/plugins/spatial_editor_plugin.cpp b/editor/plugins/spatial_editor_plugin.cpp
index 5b713ef3c4..30fff474d7 100644
--- a/editor/plugins/spatial_editor_plugin.cpp
+++ b/editor/plugins/spatial_editor_plugin.cpp
@@ -32,7 +32,7 @@
#include "camera_matrix.h"
#include "core/os/input.h"
-#include "editor/animation_editor.h"
+
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/plugins/animation_player_editor_plugin.h"
@@ -217,7 +217,7 @@ bool SpatialEditorGizmo::intersect_frustum(const Camera *p_camera, const Vector<
return false;
}
-bool SpatialEditorGizmo::intersect_ray(const Camera *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal, int *r_gizmo_handle, bool p_sec_first) {
+bool SpatialEditorGizmo::intersect_ray(Camera *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal, int *r_gizmo_handle, bool p_sec_first) {
return false;
}
@@ -320,24 +320,20 @@ void SpatialEditorViewport::_select_clicked(bool p_append, bool p_single) {
void SpatialEditorViewport::_select(Spatial *p_node, bool p_append, bool p_single) {
if (!p_append) {
+ editor_selection->clear();
+ }
- // should not modify the selection..
+ if (editor_selection->is_selected(p_node)) {
+ //erase
+ editor_selection->remove_node(p_node);
+ } else {
- editor_selection->clear();
editor_selection->add_node(p_node);
+ }
+ if (p_single) {
if (Engine::get_singleton()->is_editor_hint())
editor->call("edit_node", p_node);
-
- } else {
-
- if (editor_selection->is_selected(p_node) && p_single) {
- //erase
- editor_selection->remove_node(p_node);
- } else {
-
- editor_selection->add_node(p_node);
- }
}
}
@@ -376,7 +372,7 @@ ObjectID SpatialEditorViewport::_select_ray(const Point2 &p_pos, bool p_append,
Vector3 normal;
int handle = -1;
- bool inters = seg->intersect_ray(camera, p_pos, point, normal, NULL, p_alt_select);
+ bool inters = seg->intersect_ray(camera, p_pos, point, normal, &handle, p_alt_select);
if (!inters)
continue;
@@ -475,7 +471,7 @@ void SpatialEditorViewport::_find_items_at_pos(const Point2 &p_pos, bool &r_incl
Vector3 SpatialEditorViewport::_get_screen_to_space(const Vector3 &p_vector3) {
CameraMatrix cm;
- cm.set_perspective(get_fov(), get_size().aspect(), get_znear(), get_zfar());
+ cm.set_perspective(get_fov(), get_size().aspect(), get_znear() + p_vector3.z, get_zfar());
float screen_w, screen_h;
cm.get_viewport_size(screen_w, screen_h);
@@ -485,7 +481,7 @@ Vector3 SpatialEditorViewport::_get_screen_to_space(const Vector3 &p_vector3) {
camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);
camera_transform.translate(0, 0, cursor.distance);
- return camera_transform.xform(Vector3(((p_vector3.x / get_size().width) * 2.0 - 1.0) * screen_w, ((1.0 - (p_vector3.y / get_size().height)) * 2.0 - 1.0) * screen_h, -get_znear()));
+ return camera_transform.xform(Vector3(((p_vector3.x / get_size().width) * 2.0 - 1.0) * screen_w, ((1.0 - (p_vector3.y / get_size().height)) * 2.0 - 1.0) * screen_h, -(get_znear() + p_vector3.z)));
}
void SpatialEditorViewport::_select_region() {
@@ -493,23 +489,25 @@ void SpatialEditorViewport::_select_region() {
if (cursor.region_begin == cursor.region_end)
return; //nothing really
+ float z_offset = MAX(0.0, 5.0 - get_znear());
+
Vector3 box[4] = {
Vector3(
MIN(cursor.region_begin.x, cursor.region_end.x),
MIN(cursor.region_begin.y, cursor.region_end.y),
- 0),
+ z_offset),
Vector3(
MAX(cursor.region_begin.x, cursor.region_end.x),
MIN(cursor.region_begin.y, cursor.region_end.y),
- 0),
+ z_offset),
Vector3(
MAX(cursor.region_begin.x, cursor.region_end.x),
MAX(cursor.region_begin.y, cursor.region_end.y),
- 0),
+ z_offset),
Vector3(
MIN(cursor.region_begin.x, cursor.region_end.x),
MAX(cursor.region_begin.y, cursor.region_end.y),
- 0)
+ z_offset)
};
Vector<Plane> frustum;
@@ -529,7 +527,7 @@ void SpatialEditorViewport::_select_region() {
frustum.push_back(near);
Plane far = -near;
- far.d += 500.0;
+ far.d += get_zfar();
frustum.push_back(far);
@@ -544,19 +542,26 @@ void SpatialEditorViewport::_select_region() {
if (!sp)
continue;
+ Spatial *root_sp = sp;
+ while (root_sp && root_sp != edited_scene && root_sp->get_owner() != edited_scene && !edited_scene->is_editable_instance(root_sp->get_owner())) {
+ root_sp = Object::cast_to<Spatial>(root_sp->get_owner());
+ }
+
+ if (selected.find(root_sp) != -1) continue;
+
Ref<SpatialEditorGizmo> seg = sp->get_gizmo();
if (!seg.is_valid())
continue;
- Spatial *root_sp = sp;
- while (root_sp && root_sp != edited_scene && root_sp->get_owner() != edited_scene && !edited_scene->is_editable_instance(root_sp->get_owner())) {
- root_sp = Object::cast_to<Spatial>(root_sp->get_owner());
+ if (seg->intersect_frustum(camera, frustum)) {
+ selected.push_back(root_sp);
}
+ }
- if (selected.find(root_sp) == -1)
- if (seg->intersect_frustum(camera, frustum))
- _select(root_sp, true, false);
+ bool single = selected.size() == 1;
+ for (int i = 0; i < selected.size(); i++) {
+ _select(selected[i], true, single);
}
}
@@ -1170,6 +1175,9 @@ void SpatialEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
if (cursor.region_select) {
+
+ if (!clicked_wants_append) _clear_selected();
+
_select_region();
cursor.region_select = false;
surface->update();
@@ -1279,7 +1287,6 @@ void SpatialEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
if (cursor.region_select && nav_mode == NAVIGATION_NONE) {
-
cursor.region_end = m->get_position();
surface->update();
return;
@@ -1829,7 +1836,7 @@ void SpatialEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
if (!get_selected_count() || _edit.mode != TRANSFORM_NONE)
return;
- if (!AnimationPlayerEditor::singleton->get_key_editor()->has_keying()) {
+ if (!AnimationPlayerEditor::singleton->get_track_editor()->has_keying()) {
set_message(TTR("Keying is disabled (no key inserted)."));
return;
}
@@ -2153,10 +2160,7 @@ void SpatialEditorViewport::_notification(int p_what) {
VisualInstance *vi = Object::cast_to<VisualInstance>(sp);
- if (se->aabb.has_no_surface()) {
-
- se->aabb = vi ? vi->get_aabb() : AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4));
- }
+ se->aabb = vi ? vi->get_aabb() : AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4));
Transform t = sp->get_global_gizmo_transform();
t.translate(se->aabb.position);
diff --git a/editor/plugins/spatial_editor_plugin.h b/editor/plugins/spatial_editor_plugin.h
index 7736db67b1..637926a913 100644
--- a/editor/plugins/spatial_editor_plugin.h
+++ b/editor/plugins/spatial_editor_plugin.h
@@ -62,7 +62,7 @@ public:
virtual void commit_handle(int p_idx, const Variant &p_restore, bool p_cancel = false);
virtual bool intersect_frustum(const Camera *p_camera, const Vector<Plane> &p_frustum);
- virtual bool intersect_ray(const Camera *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal, int *r_gizmo_handle = NULL, bool p_sec_first = false);
+ virtual bool intersect_ray(Camera *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal, int *r_gizmo_handle = NULL, bool p_sec_first = false);
SpatialEditorGizmo();
};
diff --git a/editor/plugins/tile_map_editor_plugin.cpp b/editor/plugins/tile_map_editor_plugin.cpp
index 72b3af5a09..7264af3488 100644
--- a/editor/plugins/tile_map_editor_plugin.cpp
+++ b/editor/plugins/tile_map_editor_plugin.cpp
@@ -35,6 +35,7 @@
#include "editor/editor_settings.h"
#include "os/input.h"
#include "os/keyboard.h"
+#include "scene/gui/split_container.h"
void TileMapEditor::_notification(int p_what) {
@@ -132,16 +133,14 @@ void TileMapEditor::_menu_option(int p_option) {
if (!selection_active)
return;
- undo_redo->create_action(TTR("Erase Selection"));
- undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data"));
+ _start_undo(TTR("Erase Selection"));
for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
_set_cell(Point2i(j, i), TileMap::INVALID_CELL, false, false, false);
}
}
- undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data"));
- undo_redo->commit_action();
+ _finish_undo();
selection_active = false;
copydata.clear();
@@ -168,6 +167,13 @@ void TileMapEditor::_menu_option(int p_option) {
}
}
+void TileMapEditor::_palette_selected(int index) {
+
+ if (manual_autotile) {
+ _update_palette();
+ }
+}
+
void TileMapEditor::_canvas_mouse_enter() {
mouse_over = true;
@@ -200,6 +206,46 @@ void TileMapEditor::set_selected_tile(int p_tile) {
}
}
+void TileMapEditor::_create_set_cell_undo(const Vector2 &p_vec, const CellOp &p_cell_old, const CellOp &p_cell_new) {
+
+ Dictionary cell_old;
+ Dictionary cell_new;
+
+ cell_old["id"] = p_cell_old.idx;
+ cell_old["flip_h"] = p_cell_old.xf;
+ cell_old["flip_y"] = p_cell_old.yf;
+ cell_old["transpose"] = p_cell_old.tr;
+ cell_old["auto_coord"] = p_cell_old.ac;
+
+ cell_new["id"] = p_cell_new.idx;
+ cell_new["flip_h"] = p_cell_new.xf;
+ cell_new["flip_y"] = p_cell_new.yf;
+ cell_new["transpose"] = p_cell_new.tr;
+ cell_new["auto_coord"] = p_cell_new.ac;
+
+ undo_redo->add_undo_method(node, "set_celld", p_vec, cell_old);
+ undo_redo->add_do_method(node, "set_celld", p_vec, cell_new);
+}
+
+void TileMapEditor::_start_undo(const String &p_action) {
+
+ undo_data.clear();
+ undo_redo->create_action(p_action);
+}
+
+void TileMapEditor::_finish_undo() {
+
+ if (undo_data.size()) {
+ for (Map<Point2i, CellOp>::Element *E = undo_data.front(); E; E = E->next()) {
+ _create_set_cell_undo(E->key(), E->get(), _get_op_from_cell(E->key()));
+ }
+
+ undo_data.clear();
+ }
+
+ undo_redo->commit_action();
+}
+
void TileMapEditor::_set_cell(const Point2i &p_pos, int p_value, bool p_flip_h, bool p_flip_v, bool p_transpose) {
ERR_FAIL_COND(!node);
@@ -209,12 +255,46 @@ void TileMapEditor::_set_cell(const Point2i &p_pos, int p_value, bool p_flip_h,
bool prev_flip_h = node->is_cell_x_flipped(p_pos.x, p_pos.y);
bool prev_flip_v = node->is_cell_y_flipped(p_pos.x, p_pos.y);
bool prev_transpose = node->is_cell_transposed(p_pos.x, p_pos.y);
+ Vector2 prev_position = node->get_cell_autotile_coord(p_pos.x, p_pos.y);
- if (p_value == prev_val && p_flip_h == prev_flip_h && p_flip_v == prev_flip_v && p_transpose == prev_transpose)
+ Vector2 position;
+ int current = manual_palette->get_current();
+ if (current != -1) {
+ position = manual_palette->get_item_metadata(current);
+ } else {
+ // if there is no manual tile selected, that either means that
+ // autotiling is enabled, or the given tile is not autotiling. Either
+ // way, the coordinate of the tile does not matter, so assigning it to
+ // the coordinate of the existing tile works fine.
+ position = prev_position;
+ }
+
+ if (p_value == prev_val && p_flip_h == prev_flip_h && p_flip_v == prev_flip_v && p_transpose == prev_transpose && prev_position == position)
return; //check that it's actually different
+ for (int y = p_pos.y - 1; y <= p_pos.y + 1; y++) {
+ for (int x = p_pos.x - 1; x <= p_pos.x + 1; x++) {
+ Point2i p = Point2i(x, y);
+ if (!undo_data.has(p)) {
+ undo_data[p] = _get_op_from_cell(p);
+ }
+ }
+ }
+
node->set_cell(p_pos.x, p_pos.y, p_value, p_flip_h, p_flip_v, p_transpose);
- node->update_bitmask_area(Point2(p_pos));
+ if (manual_autotile) {
+ if (current != -1) {
+ node->set_cell_autotile_coord(p_pos.x, p_pos.y, position);
+ }
+ } else {
+ // manually placing tiles should not update bitmasks
+ node->update_bitmask_area(Point2(p_pos));
+ }
+}
+
+void TileMapEditor::_manual_toggled(bool p_enabled) {
+ manual_autotile = p_enabled;
+ _update_palette();
}
void TileMapEditor::_text_entered(const String &p_text) {
@@ -261,6 +341,8 @@ void TileMapEditor::_update_palette() {
int selected = get_selected_tile();
palette->clear();
+ manual_palette->clear();
+ manual_palette->hide();
Ref<TileSet> tileset = node->get_tileset();
if (tileset.is_null())
@@ -268,7 +350,6 @@ void TileMapEditor::_update_palette() {
List<int> tiles;
tileset->get_tile_list(&tiles);
-
if (tiles.empty())
return;
@@ -284,6 +365,9 @@ void TileMapEditor::_update_palette() {
palette->set_fixed_icon_size(Size2(min_size, min_size));
palette->set_fixed_column_width(min_size * MAX(size_slider->get_value(), 1));
+ palette->set_same_column_width(true);
+ manual_palette->set_fixed_icon_size(Size2(min_size, min_size));
+ manual_palette->set_same_column_width(true);
String filter = search_box->get_text().strip_edges();
@@ -344,12 +428,51 @@ void TileMapEditor::_update_palette() {
palette->set_item_metadata(palette->get_item_count() - 1, entries[i].id);
}
- palette->set_same_column_width(true);
-
if (selected != -1)
set_selected_tile(selected);
else
palette->select(0);
+
+ if (manual_autotile && tileset->tile_get_tile_mode(get_selected_tile()) == TileSet::AUTO_TILE) {
+
+ const Map<Vector2, uint16_t> &tiles = tileset->autotile_get_bitmask_map(get_selected_tile());
+
+ Vector<Vector2> entries;
+ for (const Map<Vector2, uint16_t>::Element *E = tiles.front(); E; E = E->next()) {
+ entries.push_back(E->key());
+ }
+ entries.sort();
+
+ Ref<Texture> tex = tileset->tile_get_texture(get_selected_tile());
+
+ for (int i = 0; i < entries.size(); i++) {
+
+ manual_palette->add_item(String());
+
+ if (tex.is_valid()) {
+
+ Rect2 region = tileset->tile_get_region(get_selected_tile());
+ int spacing = tileset->autotile_get_spacing(get_selected_tile());
+ region.size = tileset->autotile_get_size(get_selected_tile());
+ region.position += (region.size + Vector2(spacing, spacing)) * entries[i];
+
+ if (!region.has_no_area())
+ manual_palette->set_item_icon_region(manual_palette->get_item_count() - 1, region);
+
+ manual_palette->set_item_icon(manual_palette->get_item_count() - 1, tex);
+ }
+
+ manual_palette->set_item_metadata(manual_palette->get_item_count() - 1, entries[i]);
+ }
+ }
+
+ if (manual_palette->get_item_count() > 0) {
+ // Only show the manual palette if at least tile exists in it
+ int selected = manual_palette->get_current();
+ if (selected == -1) selected = 0;
+ manual_palette->set_current(selected);
+ manual_palette->show();
+ }
}
void TileMapEditor::_pick_tile(const Point2 &p_pos) {
@@ -533,9 +656,17 @@ void TileMapEditor::_draw_cell(int p_cell, const Point2i &p_point, bool p_flip_h
Rect2 r = node->get_tileset()->tile_get_region(p_cell);
if (node->get_tileset()->tile_get_tile_mode(p_cell) == TileSet::AUTO_TILE) {
+ Vector2 offset;
+ int selected = manual_palette->get_current();
+ if (manual_autotile && selected != -1) {
+ offset = manual_palette->get_item_metadata(selected);
+ } else {
+ offset = node->get_tileset()->autotile_get_icon_coordinate(p_cell);
+ }
+
int spacing = node->get_tileset()->autotile_get_spacing(p_cell);
r.size = node->get_tileset()->autotile_get_size(p_cell);
- r.position += (r.size + Vector2(spacing, spacing)) * node->get_tileset()->autotile_get_icon_coordinate(p_cell);
+ r.position += (r.size + Vector2(spacing, spacing)) * offset;
}
Size2 sc = p_xform.get_scale();
@@ -760,8 +891,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
tool = TOOL_PAINTING;
- undo_redo->create_action(TTR("Paint TileMap"));
- undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data"));
+ _start_undo(TTR("Paint TileMap"));
}
} else if (tool == TOOL_PICKING) {
@@ -785,8 +915,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
if (id != TileMap::INVALID_CELL) {
_set_cell(over_tile, id, flip_h, flip_v, transpose);
- undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data"));
- undo_redo->commit_action();
+ _finish_undo();
paint_undo.clear();
}
@@ -796,14 +925,12 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
if (id != TileMap::INVALID_CELL) {
- undo_redo->create_action(TTR("Line Draw"));
- undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data"));
+ _start_undo(TTR("Line Draw"));
for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) {
_set_cell(E->key(), id, flip_h, flip_v, transpose);
}
- undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data"));
- undo_redo->commit_action();
+ _finish_undo();
paint_undo.clear();
@@ -815,16 +942,14 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
if (id != TileMap::INVALID_CELL) {
- undo_redo->create_action(TTR("Rectangle Paint"));
- undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data"));
+ _start_undo(TTR("Rectangle Paint"));
for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
_set_cell(Point2i(j, i), id, flip_h, flip_v, transpose);
}
}
- undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data"));
- undo_redo->commit_action();
+ _finish_undo();
canvas_item_editor->update();
}
@@ -832,14 +957,12 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
Point2 ofs = over_tile - rectangle.position;
- undo_redo->create_action(TTR("Duplicate"));
- undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data"));
+ _start_undo(TTR("Duplicate"));
for (List<TileData>::Element *E = copydata.front(); E; E = E->next()) {
_set_cell(E->get().pos + ofs, E->get().cell, E->get().flip_h, E->get().flip_v, E->get().transpose);
}
- undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data"));
- undo_redo->commit_action();
+ _finish_undo();
copydata.clear();
@@ -848,8 +971,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
Point2 ofs = over_tile - rectangle.position;
- undo_redo->create_action(TTR("Move"));
- undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data"));
+ _start_undo(TTR("Move"));
for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
@@ -860,8 +982,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
_set_cell(E->get().pos + ofs, E->get().cell, E->get().flip_h, E->get().flip_v, E->get().transpose);
}
- undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data"));
- undo_redo->commit_action();
+ _finish_undo();
copydata.clear();
selection_active = false;
@@ -880,7 +1001,6 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
return false;
undo_redo->create_action(TTR("Bucket Fill"));
- undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data"));
Dictionary op;
op["id"] = get_selected_tile();
@@ -890,7 +1010,6 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
_fill_points(points, op);
- undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data"));
undo_redo->commit_action();
// We want to keep the bucket-tool active
@@ -942,8 +1061,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
Point2 local = node->world_to_map(xform_inv.xform(mb->get_position()));
- undo_redo->create_action(TTR("Erase TileMap"));
- undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data"));
+ _start_undo(TTR("Erase TileMap"));
if (mb->get_shift()) {
#ifdef APPLE_STYLE_KEYS
@@ -970,8 +1088,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
} else {
if (tool == TOOL_ERASING || tool == TOOL_RECTANGLE_ERASE || tool == TOOL_LINE_ERASE) {
- undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data"));
- undo_redo->commit_action();
+ _finish_undo();
if (tool == TOOL_RECTANGLE_ERASE || tool == TOOL_LINE_ERASE) {
canvas_item_editor->update();
@@ -1503,12 +1620,14 @@ void TileMapEditor::_tileset_settings_changed() {
void TileMapEditor::_icon_size_changed(float p_value) {
if (node) {
palette->set_icon_scale(p_value);
+ manual_palette->set_icon_scale(p_value);
_update_palette();
}
}
void TileMapEditor::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_manual_toggled"), &TileMapEditor::_manual_toggled);
ClassDB::bind_method(D_METHOD("_text_entered"), &TileMapEditor::_text_entered);
ClassDB::bind_method(D_METHOD("_text_changed"), &TileMapEditor::_text_changed);
ClassDB::bind_method(D_METHOD("_sbox_input"), &TileMapEditor::_sbox_input);
@@ -1517,6 +1636,7 @@ void TileMapEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_canvas_mouse_exit"), &TileMapEditor::_canvas_mouse_exit);
ClassDB::bind_method(D_METHOD("_tileset_settings_changed"), &TileMapEditor::_tileset_settings_changed);
ClassDB::bind_method(D_METHOD("_update_transform_buttons"), &TileMapEditor::_update_transform_buttons);
+ ClassDB::bind_method(D_METHOD("_palette_selected"), &TileMapEditor::_palette_selected);
ClassDB::bind_method(D_METHOD("_fill_points"), &TileMapEditor::_fill_points);
ClassDB::bind_method(D_METHOD("_erase_points"), &TileMapEditor::_erase_points);
@@ -1534,6 +1654,7 @@ TileMapEditor::CellOp TileMapEditor::_get_op_from_cell(const Point2i &p_pos) {
op.yf = true;
if (node->is_cell_transposed(p_pos.x, p_pos.y))
op.tr = true;
+ op.ac = node->get_cell_autotile_coord(p_pos.x, p_pos.y);
}
return op;
}
@@ -1574,6 +1695,8 @@ void TileMapEditor::_update_transform_buttons(Object *p_button) {
TileMapEditor::TileMapEditor(EditorNode *p_editor) {
node = NULL;
+ manual_autotile = false;
+ manual_position = Vector2(0, 0);
canvas_item_editor = NULL;
editor = p_editor;
undo_redo = editor->get_undo_redo();
@@ -1601,6 +1724,11 @@ TileMapEditor::TileMapEditor(EditorNode *p_editor) {
HBoxContainer *tool_hb2 = memnew(HBoxContainer);
add_child(tool_hb2);
+ manual_button = memnew(CheckBox);
+ manual_button->set_text("Disable Autotile");
+ manual_button->connect("toggled", this, "_manual_toggled");
+ add_child(manual_button);
+
search_box = memnew(LineEdit);
search_box->set_h_size_flags(SIZE_EXPAND_FILL);
search_box->connect("text_entered", this, "_text_entered");
@@ -1619,14 +1747,30 @@ TileMapEditor::TileMapEditor(EditorNode *p_editor) {
int mw = EDITOR_DEF("editors/tile_map/palette_min_width", 80);
+ VSplitContainer *palette_container = memnew(VSplitContainer);
+ palette_container->set_v_size_flags(SIZE_EXPAND_FILL);
+ palette_container->set_custom_minimum_size(Size2(mw, 0));
+ add_child(palette_container);
+
// Add tile palette
palette = memnew(ItemList);
+ palette->set_h_size_flags(SIZE_EXPAND_FILL);
palette->set_v_size_flags(SIZE_EXPAND_FILL);
- palette->set_custom_minimum_size(Size2(mw, 0));
palette->set_max_columns(0);
palette->set_icon_mode(ItemList::ICON_MODE_TOP);
palette->set_max_text_lines(2);
- add_child(palette);
+ palette->connect("item_selected", this, "_palette_selected");
+ palette_container->add_child(palette);
+
+ // Add autotile override palette
+ manual_palette = memnew(ItemList);
+ manual_palette->set_h_size_flags(SIZE_EXPAND_FILL);
+ manual_palette->set_v_size_flags(SIZE_EXPAND_FILL);
+ manual_palette->set_max_columns(0);
+ manual_palette->set_icon_mode(ItemList::ICON_MODE_TOP);
+ manual_palette->set_max_text_lines(2);
+ manual_palette->hide();
+ palette_container->add_child(manual_palette);
// Add menu items
toolbar = memnew(HBoxContainer);
diff --git a/editor/plugins/tile_map_editor_plugin.h b/editor/plugins/tile_map_editor_plugin.h
index 642870aec0..77e9a33892 100644
--- a/editor/plugins/tile_map_editor_plugin.h
+++ b/editor/plugins/tile_map_editor_plugin.h
@@ -35,6 +35,7 @@
#include "editor/editor_plugin.h"
#include "scene/2d/tile_map.h"
+#include "scene/gui/check_box.h"
#include "scene/gui/label.h"
#include "scene/gui/line_edit.h"
#include "scene/gui/menu_button.h"
@@ -77,6 +78,8 @@ class TileMapEditor : public VBoxContainer {
};
TileMap *node;
+ bool manual_autotile;
+ Vector2 manual_position;
EditorNode *editor;
UndoRedo *undo_redo;
@@ -85,6 +88,7 @@ class TileMapEditor : public VBoxContainer {
LineEdit *search_box;
HSlider *size_slider;
ItemList *palette;
+ ItemList *manual_palette;
HBoxContainer *toolbar;
@@ -97,6 +101,7 @@ class TileMapEditor : public VBoxContainer {
ToolButton *rotate_90;
ToolButton *rotate_180;
ToolButton *rotate_270;
+ CheckBox *manual_button;
Tool tool;
@@ -124,6 +129,7 @@ class TileMapEditor : public VBoxContainer {
bool xf;
bool yf;
bool tr;
+ Vector2 ac;
CellOp() :
idx(TileMap::INVALID_CELL),
@@ -150,6 +156,8 @@ class TileMapEditor : public VBoxContainer {
List<TileData> copydata;
+ Map<Point2i, CellOp> undo_data;
+
void _pick_tile(const Point2 &p_pos);
PoolVector<Vector2> _bucket_fill(const Point2i &p_start, bool erase = false, bool preview = false);
@@ -168,12 +176,17 @@ class TileMapEditor : public VBoxContainer {
int get_selected_tile() const;
void set_selected_tile(int p_tile);
+ void _manual_toggled(bool p_enabled);
void _text_entered(const String &p_text);
void _text_changed(const String &p_text);
void _sbox_input(const Ref<InputEvent> &p_ie);
void _update_palette();
void _menu_option(int p_option);
+ void _palette_selected(int index);
+ void _start_undo(const String &p_action);
+ void _finish_undo();
+ void _create_set_cell_undo(const Vector2 &p_vec, const CellOp &p_cell_old, const CellOp &p_cell_new);
void _set_cell(const Point2i &p_pos, int p_value, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false);
void _canvas_mouse_enter();
diff --git a/editor/plugins/tile_set_editor_plugin.cpp b/editor/plugins/tile_set_editor_plugin.cpp
index c79cf02062..087c4293f1 100644
--- a/editor/plugins/tile_set_editor_plugin.cpp
+++ b/editor/plugins/tile_set_editor_plugin.cpp
@@ -123,10 +123,10 @@ void TileSetEditor::_import_node(Node *p_node, Ref<TileSet> p_library) {
for (List<uint32_t>::Element *E = shapes.front(); E; E = E->next()) {
if (sb->is_shape_owner_disabled(E->get())) continue;
- Transform2D shape_transform = sb->shape_owner_get_transform(E->get());
+ Transform2D shape_transform = sb->get_transform() * sb->shape_owner_get_transform(E->get());
bool one_way = sb->is_shape_owner_one_way_collision_enabled(E->get());
- shape_transform[2] -= phys_offset - sb->get_transform().xform(shape_transform[2]);
+ shape_transform[2] -= phys_offset;
for (int k = 0; k < sb->shape_owner_get_shape_count(E->get()); k++) {