diff options
Diffstat (limited to 'editor/plugins')
34 files changed, 7420 insertions, 863 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..c00ad451fa --- /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 / EDSCALE); + + 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..23eeef9f20 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,11 +323,14 @@ 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()); + + AnimationPlayerEditor::singleton->get_track_editor()->update_keying(); + EditorNode::get_singleton()->update_keying(); } void AnimationPlayerEditor::_animation_new() { @@ -704,16 +713,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 +819,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 +837,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); @@ -842,8 +853,11 @@ void AnimationPlayerEditor::_update_player() { active_idx = animation->get_item_count() - 1; } - if (!player) + if (!player) { + AnimationPlayerEditor::singleton->get_track_editor()->update_keying(); + EditorNode::get_singleton()->update_keying(); return; + } updating = false; if (active_idx != -1) { @@ -856,6 +870,8 @@ void AnimationPlayerEditor::_update_player() { animation->select(0); autoplay->set_pressed(animation->get_item_text(0) == player->get_autoplay()); _animation_selected(0); + } else { + _animation_selected(0); } //pause->set_pressed(player->is_paused()); @@ -863,10 +879,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 +900,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 +1040,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 +1100,58 @@ 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; + if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) + current = animation->get_item_text(animation->get_selected()); + + Ref<Animation> anim; + if (current != String()) { + 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 +1214,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 +1472,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 +1535,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 +1577,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 +1653,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 +1661,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 +1694,14 @@ 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); + + hb->add_child(memnew(VSeparator)); + + track_editor = memnew(AnimationTrackEditor); + + hb->add_child(track_editor->get_edit_menu()); onion_skinning = memnew(MenuButton); //onion_skinning->set_flat(false); @@ -1702,10 +1730,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 +1783,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 +1796,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 ad49ab86c9..1d20c63969 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(); @@ -544,7 +543,6 @@ void CanvasItemEditor::_get_bones_at_pos(const Point2 &p_pos, Vector<_SelectResu for (Map<BoneKey, BoneList>::Element *E = bone_list.front(); E; E = E->next()) { Node2D *from_node = Object::cast_to<Node2D>(ObjectDB::get_instance(E->key().from)); - Node2D *to_node = Object::cast_to<Node2D>(ObjectDB::get_instance(E->key().to)); Vector<Vector2> bone_shape; if (!_get_bone_shape(&bone_shape, NULL, E)) @@ -720,16 +718,17 @@ Vector2 CanvasItemEditor::_anchor_to_position(const Control *p_control, Vector2 ERR_FAIL_COND_V(!p_control, Vector2()); Transform2D parent_transform = p_control->get_transform().affine_inverse(); - Size2 parent_size = p_control->get_parent_area_size(); + Rect2 parent_rect = p_control->get_parent_anchorable_rect(); - return parent_transform.xform(Vector2(parent_size.x * anchor.x, parent_size.y * anchor.y)); + return parent_transform.xform(parent_rect.position + Vector2(parent_rect.size.x * anchor.x, parent_rect.size.y * anchor.y)); } Vector2 CanvasItemEditor::_position_to_anchor(const Control *p_control, Vector2 position) { ERR_FAIL_COND_V(!p_control, Vector2()); - Size2 parent_size = p_control->get_parent_area_size(); - return p_control->get_transform().xform(position) / parent_size; + Rect2 parent_rect = p_control->get_parent_anchorable_rect(); + + return (p_control->get_transform().xform(position) - parent_rect.position) / parent_rect.size; } void CanvasItemEditor::_save_canvas_item_state(List<CanvasItem *> p_canvas_items, bool save_bones) { @@ -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; @@ -2471,10 +2491,12 @@ void CanvasItemEditor::_draw_selection() { Transform2D parent_transform = xform * control->get_transform().affine_inverse(); float node_pos_in_parent[4]; - node_pos_in_parent[0] = control->get_anchor(MARGIN_LEFT) * control->get_parent_area_size().width + control->get_margin(MARGIN_LEFT); - node_pos_in_parent[1] = control->get_anchor(MARGIN_TOP) * control->get_parent_area_size().height + control->get_margin(MARGIN_TOP); - node_pos_in_parent[2] = control->get_anchor(MARGIN_RIGHT) * control->get_parent_area_size().width + control->get_margin(MARGIN_RIGHT); - node_pos_in_parent[3] = control->get_anchor(MARGIN_BOTTOM) * control->get_parent_area_size().height + control->get_margin(MARGIN_BOTTOM); + Rect2 parent_rect = control->get_parent_anchorable_rect(); + + node_pos_in_parent[0] = control->get_anchor(MARGIN_LEFT) * parent_rect.size.width + control->get_margin(MARGIN_LEFT) + parent_rect.position.x; + node_pos_in_parent[1] = control->get_anchor(MARGIN_TOP) * parent_rect.size.height + control->get_margin(MARGIN_TOP) + parent_rect.position.y; + node_pos_in_parent[2] = control->get_anchor(MARGIN_RIGHT) * parent_rect.size.width + control->get_margin(MARGIN_RIGHT) + parent_rect.position.x; + node_pos_in_parent[3] = control->get_anchor(MARGIN_BOTTOM) * parent_rect.size.height + control->get_margin(MARGIN_BOTTOM) + parent_rect.position.y; Point2 start, end; switch (drag_type) { @@ -2770,26 +2792,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))) { @@ -2799,8 +2810,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)); } } @@ -3080,7 +3093,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()); @@ -3692,11 +3705,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 @@ -3723,11 +3736,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); } } } @@ -3737,11 +3750,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); } } @@ -3837,7 +3850,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()); */ } } @@ -4340,7 +4353,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { snap_button->set_toggle_mode(true); snap_button->connect("toggled", this, "_button_toggle_snap"); snap_button->set_tooltip(TTR("Toggle snapping.")); - snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_snap", TTR("Use Snap"), KEY_S)); + 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); @@ -4468,7 +4481,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { key_insert_button->set_flat(true); key_insert_button->set_focus_mode(FOCUS_NONE); key_insert_button->connect("pressed", this, "_popup_callback", varray(ANIM_INSERT_KEY)); - key_insert_button->set_tooltip(TTR("Insert Keys")); + key_insert_button->set_tooltip(TTR("Insert keys.")); key_insert_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/anim_insert_key", TTR("Insert Key"), KEY_INSERT)); animation_hb->add_child(key_insert_button); 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/cpu_particles_editor_plugin.cpp b/editor/plugins/cpu_particles_editor_plugin.cpp new file mode 100644 index 0000000000..b32f927249 --- /dev/null +++ b/editor/plugins/cpu_particles_editor_plugin.cpp @@ -0,0 +1,114 @@ +#include "cpu_particles_editor_plugin.h" +#include "editor/plugins/spatial_editor_plugin.h" + +void CPUParticlesEditor::_node_removed(Node *p_node) { + + if (p_node == node) { + node = NULL; + hide(); + } +} + +void CPUParticlesEditor::_notification(int p_notification) { + + if (p_notification == NOTIFICATION_ENTER_TREE) { + options->set_icon(options->get_popup()->get_icon("CPUParticles", "EditorIcons")); + } +} + +void CPUParticlesEditor::_menu_option(int p_option) { + + switch (p_option) { + + case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_MESH: { + + emission_file_dialog->popup_centered_ratio(); + + } break; + + case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE: { + + emission_tree_dialog->popup_centered_ratio(); + + } break; + } +} + +void CPUParticlesEditor::edit(CPUParticles *p_particles) { + + base_node = p_particles; + node = p_particles; +} + +void CPUParticlesEditor::_generate_emission_points() { + + /// hacer codigo aca + PoolVector<Vector3> points; + PoolVector<Vector3> normals; + + if (!_generate(points, normals)) { + return; + } + + if (normals.size() == 0) { + node->set_emission_shape(CPUParticles::EMISSION_SHAPE_POINTS); + node->set_emission_points(points); + } else { + node->set_emission_shape(CPUParticles::EMISSION_SHAPE_DIRECTED_POINTS); + node->set_emission_points(points); + node->set_emission_normals(normals); + } +} + +void CPUParticlesEditor::_bind_methods() { + + ClassDB::bind_method("_menu_option", &CPUParticlesEditor::_menu_option); +} + +CPUParticlesEditor::CPUParticlesEditor() { + + particles_editor_hb = memnew(HBoxContainer); + SpatialEditor::get_singleton()->add_control_to_menu_panel(particles_editor_hb); + options = memnew(MenuButton); + particles_editor_hb->add_child(options); + particles_editor_hb->hide(); + + options->set_text(TTR("CPUParticles")); + options->get_popup()->add_item(TTR("Create Emission Points From Mesh"), MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_MESH); + options->get_popup()->add_item(TTR("Create Emission Points From Node"), MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE); + options->get_popup()->connect("id_pressed", this, "_menu_option"); +} + +void CPUParticlesEditorPlugin::edit(Object *p_object) { + + particles_editor->edit(Object::cast_to<CPUParticles>(p_object)); +} + +bool CPUParticlesEditorPlugin::handles(Object *p_object) const { + + return p_object->is_class("CPUParticles"); +} + +void CPUParticlesEditorPlugin::make_visible(bool p_visible) { + + if (p_visible) { + particles_editor->show(); + particles_editor->particles_editor_hb->show(); + } else { + particles_editor->particles_editor_hb->hide(); + particles_editor->hide(); + particles_editor->edit(NULL); + } +} + +CPUParticlesEditorPlugin::CPUParticlesEditorPlugin(EditorNode *p_node) { + + editor = p_node; + particles_editor = memnew(CPUParticlesEditor); + editor->get_viewport()->add_child(particles_editor); + + particles_editor->hide(); +} + +CPUParticlesEditorPlugin::~CPUParticlesEditorPlugin() { +} diff --git a/editor/plugins/cpu_particles_editor_plugin.h b/editor/plugins/cpu_particles_editor_plugin.h new file mode 100644 index 0000000000..f47d17104d --- /dev/null +++ b/editor/plugins/cpu_particles_editor_plugin.h @@ -0,0 +1,55 @@ +#ifndef CPU_PARTICLES_EDITOR_PLUGIN_H +#define CPU_PARTICLES_EDITOR_PLUGIN_H + +#include "editor/plugins/particles_editor_plugin.h" +#include "scene/3d/cpu_particles.h" + +class CPUParticlesEditor : public ParticlesEditorBase { + + GDCLASS(CPUParticlesEditor, ParticlesEditorBase); + + enum Menu { + + MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE, + MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_MESH, + MENU_OPTION_CLEAR_EMISSION_VOLUME, + + }; + + CPUParticles *node; + + void _menu_option(int); + + friend class CPUParticlesEditorPlugin; + + virtual void _generate_emission_points(); + +protected: + void _notification(int p_notification); + void _node_removed(Node *p_node); + static void _bind_methods(); + +public: + void edit(CPUParticles *p_particles); + CPUParticlesEditor(); +}; + +class CPUParticlesEditorPlugin : public EditorPlugin { + + GDCLASS(CPUParticlesEditorPlugin, EditorPlugin); + + CPUParticlesEditor *particles_editor; + EditorNode *editor; + +public: + virtual String get_name() const { return "CPUParticles"; } + 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); + + CPUParticlesEditorPlugin(EditorNode *p_node); + ~CPUParticlesEditorPlugin(); +}; + +#endif // CPU_PARTICLES_EDITOR_PLUGIN_H diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp index d065a756b4..0d25b3685a 100644 --- a/editor/plugins/editor_preview_plugins.cpp +++ b/editor/plugins/editor_preview_plugins.cpp @@ -554,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 /////////////////////////////////////////////////////////////////////////// diff --git a/editor/plugins/editor_preview_plugins.h b/editor/plugins/editor_preview_plugins.h index 332f991b49..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 { diff --git a/editor/plugins/particles_editor_plugin.cpp b/editor/plugins/particles_editor_plugin.cpp index 7728995a99..e0325702a8 100644 --- a/editor/plugins/particles_editor_plugin.cpp +++ b/editor/plugins/particles_editor_plugin.cpp @@ -31,130 +31,10 @@ #include "particles_editor_plugin.h" #include "editor/plugins/spatial_editor_plugin.h" #include "io/resource_loader.h" +#include "scene/3d/cpu_particles.h" +bool ParticlesEditorBase::_generate(PoolVector<Vector3> &points, PoolVector<Vector3> &normals) { -void ParticlesEditor::_node_removed(Node *p_node) { - - if (p_node == node) { - node = NULL; - hide(); - } -} - -void ParticlesEditor::_node_selected(const NodePath &p_path) { - - Node *sel = get_node(p_path); - if (!sel) - return; - - VisualInstance *vi = Object::cast_to<VisualInstance>(sel); - if (!vi) { - - err_dialog->set_text(TTR("Node does not contain geometry.")); - err_dialog->popup_centered_minsize(); - return; - } - - geometry = vi->get_faces(VisualInstance::FACES_SOLID); - - if (geometry.size() == 0) { - - err_dialog->set_text(TTR("Node does not contain geometry (faces).")); - err_dialog->popup_centered_minsize(); - return; - } - - Transform geom_xform = node->get_global_transform().affine_inverse() * vi->get_global_transform(); - - int gc = geometry.size(); - PoolVector<Face3>::Write w = geometry.write(); - - for (int i = 0; i < gc; i++) { - for (int j = 0; j < 3; j++) { - w[i].vertex[j] = geom_xform.xform(w[i].vertex[j]); - } - } - - w = PoolVector<Face3>::Write(); - - emission_dialog->popup_centered(Size2(300, 130)); -} - -void ParticlesEditor::_notification(int p_notification) { - - if (p_notification == NOTIFICATION_ENTER_TREE) { - options->set_icon(options->get_popup()->get_icon("Particles", "EditorIcons")); - } -} - -void ParticlesEditor::_menu_option(int p_option) { - - switch (p_option) { - - case MENU_OPTION_GENERATE_AABB: { - generate_aabb->popup_centered_minsize(); - } break; - case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_MESH: { - - Ref<ParticlesMaterial> material = node->get_process_material(); - if (material.is_null()) { - EditorNode::get_singleton()->show_warning(TTR("A processor material of type 'ParticlesMaterial' is required.")); - return; - } - emission_file_dialog->popup_centered_ratio(); - - } break; - - case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE: { - Ref<ParticlesMaterial> material = node->get_process_material(); - if (material.is_null()) { - EditorNode::get_singleton()->show_warning(TTR("A processor material of type 'ParticlesMaterial' is required.")); - return; - } - - emission_tree_dialog->popup_centered_ratio(); - - } break; - } -} - -void ParticlesEditor::_generate_aabb() { - - float time = generate_seconds->get_value(); - - float running = 0.0; - - EditorProgress ep("gen_aabb", TTR("Generating AABB"), int(time)); - - AABB rect; - while (running < time) { - - uint64_t ticks = OS::get_singleton()->get_ticks_usec(); - ep.step("Generating...", int(running), true); - OS::get_singleton()->delay_usec(1000); - - AABB capture = node->capture_aabb(); - if (rect == AABB()) - rect = capture; - else - rect.merge_with(capture); - - running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0; - } - - node->set_visibility_aabb(rect); -} - -void ParticlesEditor::edit(Particles *p_particles) { - - node = p_particles; -} - -void ParticlesEditor::_generate_emission_points() { - - /// hacer codigo aca - PoolVector<float> points; bool use_normals = emission_fill->get_selected() == 1; - PoolVector<float> normals; if (emission_fill->get_selected() < 2) { @@ -175,7 +55,7 @@ void ParticlesEditor::_generate_emission_points() { err_dialog->set_text(TTR("Faces contain no area!")); err_dialog->popup_centered_minsize(); - return; + return false; } int emissor_count = emission_amount->get_value(); @@ -185,9 +65,9 @@ void ParticlesEditor::_generate_emission_points() { float areapos = Math::random(0.0f, area_accum); Map<float, int>::Element *E = triangle_area_map.find_closest(areapos); - ERR_FAIL_COND(!E) + ERR_FAIL_COND_V(!E, false) int index = E->get(); - ERR_FAIL_INDEX(index, geometry.size()); + ERR_FAIL_INDEX_V(index, geometry.size(), false); // ok FINALLY get face Face3 face = geometry[index]; @@ -195,15 +75,11 @@ void ParticlesEditor::_generate_emission_points() { Vector3 pos = face.get_random_point_inside(); - points.push_back(pos.x); - points.push_back(pos.y); - points.push_back(pos.z); + points.push_back(pos); if (use_normals) { Vector3 normal = face.get_plane().normal; - normals.push_back(normal.x); - normals.push_back(normal.y); - normals.push_back(normal.z); + normals.push_back(normal); } } } else { @@ -214,7 +90,7 @@ void ParticlesEditor::_generate_emission_points() { err_dialog->set_text(TTR("No faces!")); err_dialog->popup_centered_minsize(); - return; + return false; } PoolVector<Face3>::Read r = geometry.read(); @@ -276,15 +152,210 @@ void ParticlesEditor::_generate_emission_points() { Vector3 point = ofs + dir * val; - points.push_back(point.x); - points.push_back(point.y); - points.push_back(point.z); + points.push_back(point); break; } } } - int point_count = points.size() / 3; + return true; +} + +void ParticlesEditorBase::_node_selected(const NodePath &p_path) { + + Node *sel = get_node(p_path); + if (!sel) + return; + + VisualInstance *vi = Object::cast_to<VisualInstance>(sel); + if (!vi) { + + err_dialog->set_text(TTR("Node does not contain geometry.")); + err_dialog->popup_centered_minsize(); + return; + } + + geometry = vi->get_faces(VisualInstance::FACES_SOLID); + + if (geometry.size() == 0) { + + err_dialog->set_text(TTR("Node does not contain geometry (faces).")); + err_dialog->popup_centered_minsize(); + return; + } + + Transform geom_xform = base_node->get_global_transform().affine_inverse() * vi->get_global_transform(); + + int gc = geometry.size(); + PoolVector<Face3>::Write w = geometry.write(); + + for (int i = 0; i < gc; i++) { + for (int j = 0; j < 3; j++) { + w[i].vertex[j] = geom_xform.xform(w[i].vertex[j]); + } + } + + w = PoolVector<Face3>::Write(); + + emission_dialog->popup_centered(Size2(300, 130)); +} + +void ParticlesEditorBase::_bind_methods() { + + ClassDB::bind_method("_node_selected", &ParticlesEditorBase::_node_selected); + ClassDB::bind_method("_generate_emission_points", &ParticlesEditorBase::_generate_emission_points); +} + +ParticlesEditorBase::ParticlesEditorBase() { + + emission_dialog = memnew(ConfirmationDialog); + emission_dialog->set_title(TTR("Create Emitter")); + add_child(emission_dialog); + VBoxContainer *emd_vb = memnew(VBoxContainer); + emission_dialog->add_child(emd_vb); + + emission_amount = memnew(SpinBox); + emission_amount->set_min(1); + emission_amount->set_max(100000); + emission_amount->set_value(512); + emd_vb->add_margin_child(TTR("Emission Points:"), emission_amount); + + emission_fill = memnew(OptionButton); + emission_fill->add_item(TTR("Surface Points")); + emission_fill->add_item(TTR("Surface Points+Normal (Directed)")); + emission_fill->add_item(TTR("Volume")); + emd_vb->add_margin_child(TTR("Emission Source: "), emission_fill); + + emission_dialog->get_ok()->set_text(TTR("Create")); + emission_dialog->connect("confirmed", this, "_generate_emission_points"); + + err_dialog = memnew(ConfirmationDialog); + add_child(err_dialog); + + emission_file_dialog = memnew(EditorFileDialog); + add_child(emission_file_dialog); + emission_file_dialog->connect("file_selected", this, "_resource_seleted"); + emission_tree_dialog = memnew(SceneTreeDialog); + add_child(emission_tree_dialog); + emission_tree_dialog->connect("selected", this, "_node_selected"); + + List<String> extensions; + ResourceLoader::get_recognized_extensions_for_type("Mesh", &extensions); + + emission_file_dialog->clear_filters(); + for (int i = 0; i < extensions.size(); i++) { + + emission_file_dialog->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); + } + + emission_file_dialog->set_mode(EditorFileDialog::MODE_OPEN_FILE); +} + +void ParticlesEditor::_node_removed(Node *p_node) { + + if (p_node == node) { + node = NULL; + hide(); + } +} + +void ParticlesEditor::_notification(int p_notification) { + + if (p_notification == NOTIFICATION_ENTER_TREE) { + options->set_icon(options->get_popup()->get_icon("Particles", "EditorIcons")); + } +} + +void ParticlesEditor::_menu_option(int p_option) { + + switch (p_option) { + + case MENU_OPTION_GENERATE_AABB: { + generate_aabb->popup_centered_minsize(); + } break; + case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_MESH: { + + Ref<ParticlesMaterial> material = node->get_process_material(); + if (material.is_null()) { + EditorNode::get_singleton()->show_warning(TTR("A processor material of type 'ParticlesMaterial' is required.")); + return; + } + emission_file_dialog->popup_centered_ratio(); + + } break; + + case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE: { + Ref<ParticlesMaterial> material = node->get_process_material(); + if (material.is_null()) { + EditorNode::get_singleton()->show_warning(TTR("A processor material of type 'ParticlesMaterial' is required.")); + return; + } + + emission_tree_dialog->popup_centered_ratio(); + + } break; + case MENU_OPTION_CONVERT_TO_CPU_PARTICLES: { + + UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + CPUParticles *cpu_particles = memnew(CPUParticles); + cpu_particles->convert_from_particles(node); + + undo_redo->create_action("Replace Particles by CPUParticles"); + undo_redo->add_do_method(node, "replace_by", cpu_particles); + undo_redo->add_undo_method(cpu_particles, "replace_by", node); + undo_redo->add_do_reference(cpu_particles); + undo_redo->add_undo_reference(node); + undo_redo->commit_action(); + + } break; + } +} + +void ParticlesEditor::_generate_aabb() { + + float time = generate_seconds->get_value(); + + float running = 0.0; + + EditorProgress ep("gen_aabb", TTR("Generating AABB"), int(time)); + + AABB rect; + while (running < time) { + + uint64_t ticks = OS::get_singleton()->get_ticks_usec(); + ep.step("Generating...", int(running), true); + OS::get_singleton()->delay_usec(1000); + + AABB capture = node->capture_aabb(); + if (rect == AABB()) + rect = capture; + else + rect.merge_with(capture); + + running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0; + } + + node->set_visibility_aabb(rect); +} + +void ParticlesEditor::edit(Particles *p_particles) { + + base_node = p_particles; + node = p_particles; +} + +void ParticlesEditor::_generate_emission_points() { + + /// hacer codigo aca + PoolVector<Vector3> points; + PoolVector<Vector3> normals; + + if (!_generate(points, normals)) { + return; + } + + int point_count = points.size(); int w = 2048; int h = (point_count / 2048) + 1; @@ -295,8 +366,13 @@ void ParticlesEditor::_generate_emission_points() { { PoolVector<uint8_t>::Write iw = point_img.write(); zeromem(iw.ptr(), w * h * 3 * sizeof(float)); - PoolVector<float>::Read r = points.read(); - copymem(iw.ptr(), r.ptr(), point_count * sizeof(float) * 3); + PoolVector<Vector3>::Read r = points.read(); + float *wf = (float *)iw.ptr(); + for (int i = 0; i < point_count; i++) { + wf[i * 3 + 0] = r[i].x; + wf[i * 3 + 1] = r[i].y; + wf[i * 3 + 2] = r[i].z; + } } Ref<Image> image = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img)); @@ -308,7 +384,7 @@ void ParticlesEditor::_generate_emission_points() { Ref<ParticlesMaterial> material = node->get_process_material(); ERR_FAIL_COND(material.is_null()); - if (use_normals) { + if (normals.size() > 0) { material->set_emission_shape(ParticlesMaterial::EMISSION_SHAPE_DIRECTED_POINTS); material->set_emission_point_count(point_count); @@ -320,8 +396,13 @@ void ParticlesEditor::_generate_emission_points() { { PoolVector<uint8_t>::Write iw = point_img2.write(); zeromem(iw.ptr(), w * h * 3 * sizeof(float)); - PoolVector<float>::Read r = normals.read(); - copymem(iw.ptr(), r.ptr(), point_count * sizeof(float) * 3); + PoolVector<Vector3>::Read r = normals.read(); + float *wf = (float *)iw.ptr(); + for (int i = 0; i < point_count; i++) { + wf[i * 3 + 0] = r[i].x; + wf[i * 3 + 1] = r[i].y; + wf[i * 3 + 2] = r[i].z; + } } Ref<Image> image2 = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img2)); @@ -342,8 +423,6 @@ void ParticlesEditor::_generate_emission_points() { void ParticlesEditor::_bind_methods() { ClassDB::bind_method("_menu_option", &ParticlesEditor::_menu_option); - ClassDB::bind_method("_node_selected", &ParticlesEditor::_node_selected); - ClassDB::bind_method("_generate_emission_points", &ParticlesEditor::_generate_emission_points); ClassDB::bind_method("_generate_aabb", &ParticlesEditor::_generate_aabb); } @@ -360,49 +439,10 @@ ParticlesEditor::ParticlesEditor() { options->get_popup()->add_separator(); options->get_popup()->add_item(TTR("Create Emission Points From Mesh"), MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_MESH); options->get_popup()->add_item(TTR("Create Emission Points From Node"), MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE); - options->get_popup()->connect("id_pressed", this, "_menu_option"); - - emission_dialog = memnew(ConfirmationDialog); - emission_dialog->set_title(TTR("Create Emitter")); - add_child(emission_dialog); - VBoxContainer *emd_vb = memnew(VBoxContainer); - emission_dialog->add_child(emd_vb); - - emission_amount = memnew(SpinBox); - emission_amount->set_min(1); - emission_amount->set_max(100000); - emission_amount->set_value(512); - emd_vb->add_margin_child(TTR("Emission Points:"), emission_amount); - - emission_fill = memnew(OptionButton); - emission_fill->add_item(TTR("Surface Points")); - emission_fill->add_item(TTR("Surface Points+Normal (Directed)")); - emission_fill->add_item(TTR("Volume")); - emd_vb->add_margin_child(TTR("Emission Source: "), emission_fill); - - emission_dialog->get_ok()->set_text(TTR("Create")); - emission_dialog->connect("confirmed", this, "_generate_emission_points"); - - err_dialog = memnew(ConfirmationDialog); - add_child(err_dialog); - - emission_file_dialog = memnew(EditorFileDialog); - add_child(emission_file_dialog); - emission_file_dialog->connect("file_selected", this, "_resource_seleted"); - emission_tree_dialog = memnew(SceneTreeDialog); - add_child(emission_tree_dialog); - emission_tree_dialog->connect("selected", this, "_node_selected"); - - List<String> extensions; - ResourceLoader::get_recognized_extensions_for_type("Mesh", &extensions); - - emission_file_dialog->clear_filters(); - for (int i = 0; i < extensions.size(); i++) { - - emission_file_dialog->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); - } + options->get_popup()->add_separator(); + options->get_popup()->add_item(TTR("Convert to CPUParticles"), MENU_OPTION_CONVERT_TO_CPU_PARTICLES); - emission_file_dialog->set_mode(EditorFileDialog::MODE_OPEN_FILE); + options->get_popup()->connect("id_pressed", this, "_menu_option"); generate_aabb = memnew(ConfirmationDialog); generate_aabb->set_title(TTR("Generate Visibility AABB")); diff --git a/editor/plugins/particles_editor_plugin.h b/editor/plugins/particles_editor_plugin.h index 013b6e7e30..622ce6e8a9 100644 --- a/editor/plugins/particles_editor_plugin.h +++ b/editor/plugins/particles_editor_plugin.h @@ -40,14 +40,14 @@ @author Juan Linietsky <reduzio@gmail.com> */ -class ParticlesEditor : public Control { - - GDCLASS(ParticlesEditor, Control); +class ParticlesEditorBase : public Control { + GDCLASS(ParticlesEditorBase, Control) +protected: + Spatial *base_node; Panel *panel; MenuButton *options; HBoxContainer *particles_editor_hb; - Particles *node; EditorFileDialog *emission_file_dialog; SceneTreeDialog *emission_tree_dialog; @@ -58,8 +58,25 @@ class ParticlesEditor : public Control { SpinBox *emission_amount; OptionButton *emission_fill; + PoolVector<Face3> geometry; + + bool _generate(PoolVector<Vector3> &points, PoolVector<Vector3> &normals); + virtual void _generate_emission_points() = 0; + void _node_selected(const NodePath &p_path); + + static void _bind_methods(); + +public: + ParticlesEditorBase(); +}; + +class ParticlesEditor : public ParticlesEditorBase { + + GDCLASS(ParticlesEditor, ParticlesEditorBase); + ConfirmationDialog *generate_aabb; SpinBox *generate_seconds; + Particles *node; enum Menu { @@ -67,21 +84,18 @@ class ParticlesEditor : public Control { MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE, MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_MESH, MENU_OPTION_CLEAR_EMISSION_VOLUME, + MENU_OPTION_CONVERT_TO_CPU_PARTICLES, }; - PoolVector<Face3> geometry; - void _generate_aabb(); - void _generate_emission_points(); - void _node_selected(const NodePath &p_path); void _menu_option(int); - void _populate(); - friend class ParticlesEditorPlugin; + virtual void _generate_emission_points(); + protected: void _notification(int p_notification); void _node_removed(Node *p_node); diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp index ed41e1931e..4840b1899d 100644 --- a/editor/plugins/polygon_2d_editor_plugin.cpp +++ b/editor/plugins/polygon_2d_editor_plugin.cpp @@ -563,6 +563,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { if (uv_move_current == UV_MODE_REMOVE_SPLIT) { + splits_prev = node->get_splits(); for (int i = 0; i < splits_prev.size(); i += 2) { if (splits_prev[i] < 0 || splits_prev[i] >= points_prev.size()) continue; 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..876da7f61a 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); } } @@ -1853,6 +1872,7 @@ void ScriptEditor::save_all_scripts() { } _update_script_names(); + EditorFileSystem::get_singleton()->update_script_classes(); } void ScriptEditor::apply_scripts() const { @@ -2577,6 +2597,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 +2694,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 +2714,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 +2726,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 +2770,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 +2885,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..ad12add53f 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -72,9 +72,9 @@ public: class ScriptEditorDebugger; -class ScriptEditorBase : public Control { +class ScriptEditorBase : public VBoxContainer { - GDCLASS(ScriptEditorBase, Control); + GDCLASS(ScriptEditorBase, VBoxContainer); protected: static void _bind_methods(); @@ -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..ffc2203475 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1289,16 +1289,26 @@ void ScriptTextEditor::_edit_option(int p_op) { void ScriptTextEditor::add_syntax_highlighter(SyntaxHighlighter *p_highlighter) { highlighters[p_highlighter->get_name()] = p_highlighter; - highlighter_menu->get_popup()->add_item(p_highlighter->get_name()); + highlighter_menu->add_radio_check_item(p_highlighter->get_name()); } void ScriptTextEditor::set_syntax_highlighter(SyntaxHighlighter *p_highlighter) { TextEdit *te = code_editor->get_text_edit(); te->_set_syntax_highlighting(p_highlighter); + if (p_highlighter != NULL) + highlighter_menu->set_item_checked(highlighter_menu->get_item_idx_from_text(p_highlighter->get_name()), true); + else + highlighter_menu->set_item_checked(highlighter_menu->get_item_idx_from_text("Standard"), true); } void ScriptTextEditor::_change_syntax_highlighter(int p_idx) { - set_syntax_highlighter(highlighters[highlighter_menu->get_popup()->get_item_text(p_idx)]); + Map<String, SyntaxHighlighter *>::Element *el = highlighters.front(); + while (el != NULL) { + highlighter_menu->set_item_checked(highlighter_menu->get_item_idx_from_text(el->key()), false); + el = el->next(); + } + // highlighter_menu->set_item_checked(p_idx, true); + set_syntax_highlighter(highlighters[highlighter_menu->get_item_text(p_idx)]); } void ScriptTextEditor::_bind_methods() { @@ -1609,6 +1619,7 @@ ScriptTextEditor::ScriptTextEditor() { code_editor->set_code_complete_func(_code_complete_scripts, this); code_editor->get_text_edit()->connect("breakpoint_toggled", this, "_breakpoint_toggled"); code_editor->get_text_edit()->connect("symbol_lookup", this, "_lookup_symbol"); + code_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); update_settings(); @@ -1666,6 +1677,7 @@ ScriptTextEditor::ScriptTextEditor() { edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_breakpoint"), DEBUG_GOTO_NEXT_BREAKPOINT); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_breakpoint"), DEBUG_GOTO_PREV_BREAKPOINT); edit_menu->get_popup()->add_separator(); + PopupMenu *convert_case = memnew(PopupMenu); convert_case->set_name("convert_case"); edit_menu->get_popup()->add_child(convert_case); @@ -1675,6 +1687,14 @@ ScriptTextEditor::ScriptTextEditor() { convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize")), EDIT_CAPITALIZE); convert_case->connect("id_pressed", this, "_edit_option"); + highlighters["Standard"] = NULL; + highlighter_menu = memnew(PopupMenu); + highlighter_menu->set_name("highlighter_menu"); + edit_menu->get_popup()->add_child(highlighter_menu); + edit_menu->get_popup()->add_submenu_item(TTR("Syntax Highlighter"), "highlighter_menu"); + highlighter_menu->add_radio_check_item(TTR("Standard")); + highlighter_menu->connect("id_pressed", this, "_change_syntax_highlighter"); + search_menu = memnew(MenuButton); edit_hb->add_child(search_menu); search_menu->set_text(TTR("Search")); @@ -1694,14 +1714,6 @@ ScriptTextEditor::ScriptTextEditor() { edit_hb->add_child(edit_menu); - highlighters["Standard"] = NULL; - - highlighter_menu = memnew(MenuButton); - highlighter_menu->set_text(TTR("Syntax Highlighter")); - highlighter_menu->get_popup()->add_item("Standard"); - highlighter_menu->get_popup()->connect("id_pressed", this, "_change_syntax_highlighter"); - edit_hb->add_child(highlighter_menu); - quick_open = memnew(ScriptEditorQuickOpen); add_child(quick_open); quick_open->connect("goto_line", this, "_goto_line"); @@ -1727,7 +1739,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 +1752,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 +1789,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/script_text_editor.h b/editor/plugins/script_text_editor.h index a93e1a6fa8..a415f478e8 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -49,8 +49,8 @@ class ScriptTextEditor : public ScriptEditorBase { HBoxContainer *edit_hb; MenuButton *edit_menu; - MenuButton *highlighter_menu; MenuButton *search_menu; + PopupMenu *highlighter_menu; PopupMenu *context_menu; GotoLineDialog *goto_line_dialog; diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 9b31e1a421..4b7f27c0c1 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -130,9 +130,9 @@ void ShaderTextEditor::_load_theme_settings() { } } - for (const Set<String>::Element *E = ShaderTypes::get_singleton()->get_modes(VisualServer::ShaderMode(shader->get_mode())).front(); E; E = E->next()) { + for (int i = 0; i < ShaderTypes::get_singleton()->get_modes(VisualServer::ShaderMode(shader->get_mode())).size(); i++) { - keywords.push_back(E->get()); + keywords.push_back(ShaderTypes::get_singleton()->get_modes(VisualServer::ShaderMode(shader->get_mode()))[i]); } } diff --git a/editor/plugins/spatial_editor_plugin.cpp b/editor/plugins/spatial_editor_plugin.cpp index 5b713ef3c4..37b8562e96 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,11 @@ 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()); + if (orthogonal) { + cm.set_orthogonal(camera->get_size(), get_size().aspect(), get_znear() + p_vector3.z, get_zfar()); + } else { + 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 +485,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 +493,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; @@ -520,18 +522,24 @@ void SpatialEditorViewport::_select_region() { Vector3 a = _get_screen_to_space(box[i]); Vector3 b = _get_screen_to_space(box[(i + 1) % 4]); - frustum.push_back(Plane(a, b, cam_pos)); + if (orthogonal) { + frustum.push_back(Plane(a, (a - b).normalized())); + } else { + frustum.push_back(Plane(a, b, cam_pos)); + } } - Plane near(cam_pos, -_get_camera_normal()); - near.d -= get_znear(); + if (!orthogonal) { + Plane near(cam_pos, -_get_camera_normal()); + near.d -= get_znear(); - frustum.push_back(near); + frustum.push_back(near); - Plane far = -near; - far.d += 500.0; + Plane far = -near; + far.d += get_zfar(); - frustum.push_back(far); + frustum.push_back(far); + } Vector<ObjectID> instances = VisualServer::get_singleton()->instances_cull_convex(frustum, get_tree()->get_root()->get_world()->get_scenario()); Vector<Spatial *> selected; @@ -544,19 +552,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 +1185,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 +1297,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 +1846,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 +2170,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..19646f37b5 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); + _set_cell(Point2i(j, i), 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; @@ -180,41 +186,129 @@ void TileMapEditor::_canvas_mouse_exit() { canvas_item_editor->update(); } -int TileMapEditor::get_selected_tile() const { +Vector<int> TileMapEditor::get_selected_tiles() const { + + Vector<int> items = palette->get_selected_items(); + + if (items.size() == 0) { + items.push_back(TileMap::INVALID_CELL); + return items; + } + + for (int i = items.size() - 1; i >= 0; i--) { + items[i] = palette->get_item_metadata(items[i]); + } + return items; +} + +void TileMapEditor::set_selected_tiles(Vector<int> p_tiles) { + + palette->unselect_all(); + + for (int i = p_tiles.size() - 1; i >= 0; i--) { + int idx = palette->find_metadata(p_tiles[i]); + + if (idx >= 0) { + palette->select(idx, false); + } + } + + palette->ensure_current_is_visible(); +} + +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; - int item = palette->get_current(); + 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; - if (item == -1) - return TileMap::INVALID_CELL; + 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; - return palette->get_item_metadata(item); + 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::set_selected_tile(int p_tile) { +void TileMapEditor::_start_undo(const String &p_action) { - int idx = palette->find_metadata(p_tile); + undo_data.clear(); + undo_redo->create_action(p_action); +} + +void TileMapEditor::_finish_undo() { - if (idx >= 0) { - palette->select(idx, true); - palette->ensure_current_is_visible(); + 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) { +void TileMapEditor::_set_cell(const Point2i &p_pos, Vector<int> p_values, bool p_flip_h, bool p_flip_v, bool p_transpose) { ERR_FAIL_COND(!node); + if (p_values.size() == 0) + return; + + int p_value = p_values[Math::rand() % p_values.size()]; int prev_val = node->get_cell(p_pos.x, p_pos.y); 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) { @@ -259,8 +353,10 @@ void TileMapEditor::_update_palette() { if (!node) return; - int selected = get_selected_tile(); + Vector<int> selected = get_selected_tiles(); palette->clear(); + manual_palette->clear(); + manual_palette->hide(); Ref<TileSet> tileset = node->get_tileset(); if (tileset.is_null()) @@ -268,7 +364,6 @@ void TileMapEditor::_update_palette() { List<int> tiles; tileset->get_tile_list(&tiles); - if (tiles.empty()) return; @@ -284,6 +379,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 +442,54 @@ 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 + int sel_tile = selected.get(0); + if (selected.get(0) != TileMap::INVALID_CELL) { + set_selected_tiles(selected); + sel_tile = selected.get(Math::rand() % selected.size()); + } else { palette->select(0); + } + + if (manual_autotile && tileset->tile_get_tile_mode(sel_tile) == TileSet::AUTO_TILE) { + + const Map<Vector2, uint16_t> &tiles = tileset->autotile_get_bitmask_map(sel_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(sel_tile); + + for (int i = 0; i < entries.size(); i++) { + + manual_palette->add_item(String()); + + if (tex.is_valid()) { + + Rect2 region = tileset->tile_get_region(sel_tile); + int spacing = tileset->autotile_get_spacing(sel_tile); + region.size = tileset->autotile_get_size(sel_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) { @@ -365,7 +505,10 @@ void TileMapEditor::_pick_tile(const Point2 &p_pos) { _update_palette(); } - set_selected_tile(id); + Vector<int> selected; + + selected.push_back(id); + set_selected_tiles(selected); mirror_x->set_pressed(node->is_cell_x_flipped(p_pos.x, p_pos.y)); mirror_y->set_pressed(node->is_cell_y_flipped(p_pos.x, p_pos.y)); @@ -378,18 +521,21 @@ void TileMapEditor::_pick_tile(const Point2 &p_pos) { PoolVector<Vector2> TileMapEditor::_bucket_fill(const Point2i &p_start, bool erase, bool preview) { int prev_id = node->get_cell(p_start.x, p_start.y); - int id = TileMap::INVALID_CELL; + Vector<int> ids; + ids.push_back(TileMap::INVALID_CELL); if (!erase) { - id = get_selected_tile(); + ids = get_selected_tiles(); - if (id == TileMap::INVALID_CELL) + if (ids.size() == 0 && ids[0] == TileMap::INVALID_CELL) return PoolVector<Vector2>(); } else if (prev_id == TileMap::INVALID_CELL) { return PoolVector<Vector2>(); } - if (id == prev_id) { - return PoolVector<Vector2>(); + for (int i = ids.size() - 1; i >= 0; i--) { + if (ids[i] == prev_id) { + return PoolVector<Vector2>(); + } } Rect2i r = node->_edit_get_rect(); @@ -479,13 +625,13 @@ void TileMapEditor::_fill_points(const PoolVector<Vector2> p_points, const Dicti int len = p_points.size(); PoolVector<Vector2>::Read pr = p_points.read(); - int id = p_op["id"]; + Vector<int> ids = p_op["id"]; bool xf = p_op["flip_h"]; bool yf = p_op["flip_v"]; bool tr = p_op["transpose"]; for (int i = 0; i < len; i++) { - _set_cell(pr[i], id, xf, yf, tr); + _set_cell(pr[i], ids, xf, yf, tr); node->make_bitmask_area_dirty(pr[i]); } node->update_dirty_bitmask(); @@ -498,7 +644,7 @@ void TileMapEditor::_erase_points(const PoolVector<Vector2> p_points) { for (int i = 0; i < len; i++) { - _set_cell(pr[i], TileMap::INVALID_CELL); + _set_cell(pr[i], invalid_cell); } } @@ -533,9 +679,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(); @@ -754,14 +908,13 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { if (tool == TOOL_PAINTING) { - int id = get_selected_tile(); + Vector<int> ids = get_selected_tiles(); - if (id != TileMap::INVALID_CELL) { + if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) { 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) { @@ -780,30 +933,27 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { if (tool == TOOL_PAINTING) { - int id = get_selected_tile(); + Vector<int> ids = get_selected_tiles(); - if (id != TileMap::INVALID_CELL) { + if (ids.size() > 0 && ids[0] != 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(); + _set_cell(over_tile, ids, flip_h, flip_v, transpose); + _finish_undo(); paint_undo.clear(); } } else if (tool == TOOL_LINE_PAINT) { - int id = get_selected_tile(); + Vector<int> ids = get_selected_tiles(); - if (id != TileMap::INVALID_CELL) { + if (ids.size() > 0 && ids[0] != 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); + _set_cell(E->key(), ids, 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(); @@ -811,35 +961,34 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { } } else if (tool == TOOL_RECTANGLE_PAINT) { - int id = get_selected_tile(); + Vector<int> ids = get_selected_tiles(); - if (id != TileMap::INVALID_CELL) { + if (ids.size() > 0 && ids[0] != 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); + _set_cell(Point2i(j, i), ids, 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(); } } else if (tool == TOOL_DUPLICATING) { Point2 ofs = over_tile - rectangle.position; + Vector<int> ids; - undo_redo->create_action(TTR("Duplicate")); - undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data")); + _start_undo(TTR("Duplicate")); + ids.push_back(0); 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); + ids[0] = E->get().cell; + _set_cell(E->get().pos + ofs, ids, 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(); @@ -847,21 +996,22 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { } else if (tool == TOOL_MOVING) { Point2 ofs = over_tile - rectangle.position; + Vector<int> ids; - undo_redo->create_action(TTR("Move")); - undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data")); + _start_undo(TTR("Move")); + ids.push_back(TileMap::INVALID_CELL); 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); + _set_cell(Point2i(j, i), ids, false, false, false); } } 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); + ids[0] = E->get().cell; + _set_cell(E->get().pos + ofs, ids, 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,17 +1030,15 @@ 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(); + op["id"] = get_selected_tiles(); op["flip_h"] = flip_h; op["flip_v"] = flip_v; op["transpose"] = transpose; _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 +1090,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 @@ -961,7 +1108,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { tool = TOOL_ERASING; - _set_cell(local, TileMap::INVALID_CELL); + _set_cell(local, invalid_cell); } return true; @@ -970,8 +1117,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(); @@ -983,8 +1129,10 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { } else if (tool == TOOL_BUCKET) { + Vector<int> ids; + ids.push_back(node->get_cell(over_tile.x, over_tile.y)); Dictionary pop; - pop["id"] = node->get_cell(over_tile.x, over_tile.y); + pop["id"] = ids; pop["flip_h"] = node->is_cell_x_flipped(over_tile.x, over_tile.y); pop["flip_v"] = node->is_cell_y_flipped(over_tile.x, over_tile.y); pop["transpose"] = node->is_cell_transposed(over_tile.x, over_tile.y); @@ -1032,7 +1180,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { // Paint using bresenham line to prevent holes in painting if the user moves fast Vector<Point2i> points = line(old_over_tile.x, over_tile.x, old_over_tile.y, over_tile.y); - int id = get_selected_tile(); + Vector<int> ids = get_selected_tiles(); for (int i = 0; i < points.size(); ++i) { @@ -1042,7 +1190,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { paint_undo[pos] = _get_op_from_cell(pos); } - _set_cell(pos, id, flip_h, flip_v, transpose); + _set_cell(pos, ids, flip_h, flip_v, transpose); } return true; @@ -1058,7 +1206,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { Point2i pos = points[i]; - _set_cell(pos, TileMap::INVALID_CELL); + _set_cell(pos, invalid_cell); } return true; @@ -1073,20 +1221,23 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { if (tool == TOOL_LINE_PAINT || tool == TOOL_LINE_ERASE) { - int id = get_selected_tile(); + Vector<int> ids = get_selected_tiles(); + Vector<int> tmp_cell; bool erasing = (tool == TOOL_LINE_ERASE); + tmp_cell.push_back(0); if (erasing && paint_undo.size()) { for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) { - _set_cell(E->key(), E->get().idx, E->get().xf, E->get().yf, E->get().tr); + tmp_cell[0] = E->get().idx; + _set_cell(E->key(), tmp_cell, E->get().xf, E->get().yf, E->get().tr); } } paint_undo.clear(); - if (id != TileMap::INVALID_CELL) { + if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) { Vector<Point2i> points = line(rectangle_begin.x, over_tile.x, rectangle_begin.y, over_tile.y); @@ -1095,7 +1246,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { paint_undo[points[i]] = _get_op_from_cell(points[i]); if (erasing) - _set_cell(points[i], TileMap::INVALID_CELL); + _set_cell(points[i], invalid_cell); } canvas_item_editor->update(); @@ -1105,6 +1256,9 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { } if (tool == TOOL_RECTANGLE_PAINT || tool == TOOL_RECTANGLE_ERASE) { + Vector<int> tmp_cell; + tmp_cell.push_back(0); + _select(rectangle_begin, over_tile); if (tool == TOOL_RECTANGLE_ERASE) { @@ -1113,7 +1267,8 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) { - _set_cell(E->key(), E->get().idx, E->get().xf, E->get().yf, E->get().tr); + tmp_cell[0] = E->get().idx; + _set_cell(E->key(), tmp_cell, E->get().xf, E->get().yf, E->get().tr); } } @@ -1125,7 +1280,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { Point2i tile = Point2i(j, i); paint_undo[tile] = _get_op_from_cell(tile); - _set_cell(tile, TileMap::INVALID_CELL); + _set_cell(tile, invalid_cell); } } } @@ -1382,27 +1537,27 @@ void TileMapEditor::forward_draw_over_viewport(Control *p_overlay) { if (paint_undo.empty()) return; - int id = get_selected_tile(); + Vector<int> ids = get_selected_tiles(); - if (id == TileMap::INVALID_CELL) + if (ids.size() == 1 && ids[0] == TileMap::INVALID_CELL) return; for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) { - _draw_cell(id, E->key(), flip_h, flip_v, transpose, xform); + _draw_cell(ids[0], E->key(), flip_h, flip_v, transpose, xform); } } else if (tool == TOOL_RECTANGLE_PAINT) { - int id = get_selected_tile(); + Vector<int> ids = get_selected_tiles(); - if (id == TileMap::INVALID_CELL) + if (ids.size() == 1 && ids[0] == TileMap::INVALID_CELL) return; 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++) { - _draw_cell(id, Point2i(j, i), flip_h, flip_v, transpose, xform); + _draw_cell(ids[0], Point2i(j, i), flip_h, flip_v, transpose, xform); } } } else if (tool == TOOL_DUPLICATING || tool == TOOL_MOVING) { @@ -1440,17 +1595,17 @@ void TileMapEditor::forward_draw_over_viewport(Control *p_overlay) { } else if (tool == TOOL_BUCKET) { - int tile = get_selected_tile(); - _draw_fill_preview(tile, over_tile, flip_h, flip_v, transpose, xform); + Vector<int> tiles = get_selected_tiles(); + _draw_fill_preview(tiles[0], over_tile, flip_h, flip_v, transpose, xform); } else { - int st = get_selected_tile(); + Vector<int> st = get_selected_tiles(); - if (st == TileMap::INVALID_CELL) + if (st.size() == 1 && st[0] == TileMap::INVALID_CELL) return; - _draw_cell(st, over_tile, flip_h, flip_v, transpose, xform); + _draw_cell(st[0], over_tile, flip_h, flip_v, transpose, xform); } } } @@ -1503,12 +1658,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 +1674,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 +1692,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 +1733,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(); @@ -1590,6 +1751,9 @@ TileMapEditor::TileMapEditor(EditorNode *p_editor) { bucket_cache_tile = -1; bucket_cache_visited = 0; + invalid_cell.resize(1); + invalid_cell[0] = TileMap::INVALID_CELL; + ED_SHORTCUT("tile_map_editor/erase_selection", TTR("Erase Selection"), KEY_DELETE); ED_SHORTCUT("tile_map_editor/find_tile", TTR("Find Tile"), KEY_MASK_CMD + KEY_F); ED_SHORTCUT("tile_map_editor/transpose", TTR("Transpose"), KEY_T); @@ -1601,6 +1765,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 +1788,31 @@ 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->set_select_mode(ItemList::SELECT_MULTI); + 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..b8443ca962 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,9 @@ class TileMapEditor : public VBoxContainer { List<TileData> copydata; + Map<Point2i, CellOp> undo_data; + Vector<int> invalid_cell; + void _pick_tile(const Point2 &p_pos); PoolVector<Vector2> _bucket_fill(const Point2i &p_start, bool erase = false, bool preview = false); @@ -165,16 +174,21 @@ class TileMapEditor : public VBoxContainer { void _update_copydata(); - int get_selected_tile() const; - void set_selected_tile(int p_tile); + Vector<int> get_selected_tiles() const; + void set_selected_tiles(Vector<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 _set_cell(const Point2i &p_pos, int p_value, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false); + 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, Vector<int> p_values, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false); void _canvas_mouse_enter(); void _canvas_mouse_exit(); 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++) { diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp new file mode 100644 index 0000000000..682ca744ff --- /dev/null +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -0,0 +1,1217 @@ +#include "visual_shader_editor_plugin.h" + +#include "core/io/resource_loader.h" +#include "core/project_settings.h" +#include "editor/editor_properties.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" + +Control *VisualShaderNodePlugin::create_editor(const Ref<VisualShaderNode> &p_node) { + + if (get_script_instance()) { + return get_script_instance()->call("create_editor", p_node); + } + return NULL; +} + +void VisualShaderNodePlugin::_bind_methods() { + + BIND_VMETHOD(MethodInfo(Variant::OBJECT, "create_editor", PropertyInfo(Variant::OBJECT, "for_node", PROPERTY_HINT_RESOURCE_TYPE, "VisualShaderNode"))); +} + +/////////////////// + +void VisualShaderEditor::edit(VisualShader *p_visual_shader) { + + if (p_visual_shader) { + visual_shader = Ref<VisualShader>(p_visual_shader); + } else { + visual_shader.unref(); + } + + if (visual_shader.is_null()) { + hide(); + } else { + _update_graph(); + } +} + +void VisualShaderEditor::add_plugin(const Ref<VisualShaderNodePlugin> &p_plugin) { + if (plugins.find(p_plugin) != -1) + return; + plugins.push_back(p_plugin); +} + +void VisualShaderEditor::remove_plugin(const Ref<VisualShaderNodePlugin> &p_plugin) { + plugins.erase(p_plugin); +} + +void VisualShaderEditor::add_custom_type(const String &p_name, const String &p_category, 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; + ao.category = p_category; + add_options.push_back(ao); + + _update_options_menu(); +} + +void VisualShaderEditor::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 VisualShaderEditor::_update_options_menu() { + + String prev_category; + add_node->get_popup()->clear(); + for (int i = 0; i < add_options.size(); i++) { + if (prev_category != add_options[i].category) { + add_node->get_popup()->add_separator(add_options[i].category); + } + add_node->get_popup()->add_item(add_options[i].name, i); + prev_category = add_options[i].category; + } +} + +Size2 VisualShaderEditor::get_minimum_size() const { + + return Size2(10, 200); +} + +void VisualShaderEditor::_draw_color_over_button(Object *obj, Color p_color) { + + Button *button = Object::cast_to<Button>(obj); + if (!button) + return; + + Ref<StyleBox> normal = get_stylebox("normal", "Button"); + button->draw_rect(Rect2(normal->get_offset(), button->get_size() - normal->get_minimum_size()), p_color); +} + +static Ref<StyleBoxEmpty> make_empty_stylebox(float p_margin_left = -1, float p_margin_top = -1, float p_margin_right = -1, float p_margin_bottom = -1) { + Ref<StyleBoxEmpty> style(memnew(StyleBoxEmpty)); + style->set_default_margin(MARGIN_LEFT, p_margin_left * EDSCALE); + style->set_default_margin(MARGIN_RIGHT, p_margin_right * EDSCALE); + style->set_default_margin(MARGIN_BOTTOM, p_margin_bottom * EDSCALE); + style->set_default_margin(MARGIN_TOP, p_margin_top * EDSCALE); + return style; +} + +void VisualShaderEditor::_update_graph() { + + if (updating) + return; + + graph->set_scroll_ofs(visual_shader->get_graph_offset() * EDSCALE); + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + 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--; + } + } + + static const Color type_color[3] = { + Color::html("#61daf4"), + Color::html("#d67dee"), + Color::html("#f6a86e") + }; + + List<VisualShader::Connection> connections; + visual_shader->get_node_connections(type, &connections); + + Ref<StyleBoxEmpty> label_style = make_empty_stylebox(2, 1, 2, 1); + + Vector<int> nodes = visual_shader->get_node_list(type); + + for (int n_i = 0; n_i < nodes.size(); n_i++) { + + Vector2 position = visual_shader->get_node_position(type, nodes[n_i]); + Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, nodes[n_i]); + + GraphNode *node = memnew(GraphNode); + graph->add_child(node); + + /*if (!vsnode->is_connected("changed", this, "_node_changed")) { + vsnode->connect("changed", this, "_node_changed", varray(vsnode->get_instance_id()), CONNECT_DEFERRED); + }*/ + + node->set_offset(position); + + node->set_title(vsnode->get_caption()); + node->set_name(itos(nodes[n_i])); + + if (nodes[n_i] >= 2) { + node->set_show_close_button(true); + node->connect("close_request", this, "_delete_request", varray(nodes[n_i]), CONNECT_DEFERRED); + } + + node->connect("dragged", this, "_node_dragged", varray(nodes[n_i])); + + Control *custom_editor = NULL; + int port_offset = 0; + + Ref<VisualShaderNodeUniform> uniform = vsnode; + if (uniform.is_valid()) { + LineEdit *uniform_name = memnew(LineEdit); + uniform_name->set_text(uniform->get_uniform_name()); + node->add_child(uniform_name); + uniform_name->connect("text_entered", this, "_line_edit_changed", varray(uniform_name, nodes[n_i])); + uniform_name->connect("focus_exited", this, "_line_edit_focus_out", varray(uniform_name, nodes[n_i])); + + if (vsnode->get_input_port_count() == 0 && vsnode->get_output_port_count() == 1 && vsnode->get_output_port_name(0) == "") { + //shortcut + VisualShaderNode::PortType port_right = vsnode->get_output_port_type(0); + node->set_slot(0, false, VisualShaderNode::PORT_TYPE_SCALAR, Color(), true, port_right, type_color[port_right]); + continue; + } + port_offset++; + } + + for (int i = 0; i < plugins.size(); i++) { + custom_editor = plugins[i]->create_editor(vsnode); + if (custom_editor) { + break; + } + } + + if (custom_editor && vsnode->get_output_port_count() > 0 && vsnode->get_output_port_name(0) == "" && (vsnode->get_input_port_count() == 0 || vsnode->get_input_port_name(0) == "")) { + //will be embedded in first port + } else if (custom_editor) { + port_offset++; + node->add_child(custom_editor); + custom_editor = NULL; + } + + for (int i = 0; i < MAX(vsnode->get_input_port_count(), vsnode->get_output_port_count()); i++) { + + if (vsnode->is_port_separator(i)) { + node->add_child(memnew(HSeparator)); + port_offset++; + } + + bool valid_left = i < vsnode->get_input_port_count(); + VisualShaderNode::PortType port_left = VisualShaderNode::PORT_TYPE_SCALAR; + bool port_left_used = false; + String name_left; + if (valid_left) { + name_left = vsnode->get_input_port_name(i); + port_left = vsnode->get_input_port_type(i); + for (List<VisualShader::Connection>::Element *E = connections.front(); E; E = E->next()) { + if (E->get().to_node == nodes[n_i] && E->get().to_port == i) { + port_left_used = true; + } + } + } + + bool valid_right = i < vsnode->get_output_port_count(); + VisualShaderNode::PortType port_right = VisualShaderNode::PORT_TYPE_SCALAR; + String name_right; + if (valid_right) { + name_right = vsnode->get_output_port_name(i); + port_right = vsnode->get_output_port_type(i); + } + + HBoxContainer *hb = memnew(HBoxContainer); + + Variant default_value; + + if (valid_left && !port_left_used) { + default_value = vsnode->get_input_port_default_value(i); + } + + if (default_value.get_type() != Variant::NIL) { // only a label + Button *button = memnew(Button); + hb->add_child(button); + button->connect("pressed", this, "_edit_port_default_input", varray(button, nodes[n_i], i)); + + switch (default_value.get_type()) { + + case Variant::COLOR: { + button->set_custom_minimum_size(Size2(30, 0) * EDSCALE); + button->connect("draw", this, "_draw_color_over_button", varray(button, default_value)); + } break; + case Variant::INT: + case Variant::REAL: { + button->set_text(String::num(default_value, 4)); + } break; + case Variant::VECTOR3: { + Vector3 v = default_value; + button->set_text(String::num(v.x, 3) + "," + String::num(v.y, 3) + "," + String::num(v.z, 3)); + } break; + default: {} + } + } + + if (i == 0 && custom_editor) { + + hb->add_child(custom_editor); + custom_editor->set_h_size_flags(SIZE_EXPAND_FILL); + } else { + + if (valid_left) { + + Label *label = memnew(Label); + label->set_text(name_left); + label->add_style_override("normal", label_style); //more compact + hb->add_child(label); + } + + hb->add_spacer(); + + if (valid_right) { + + Label *label = memnew(Label); + label->set_text(name_right); + label->set_align(Label::ALIGN_RIGHT); + label->add_style_override("normal", label_style); //more compact + hb->add_child(label); + } + } + + if (valid_right && edit_type->get_selected() == VisualShader::TYPE_FRAGMENT) { + TextureButton *preview = memnew(TextureButton); + preview->set_toggle_mode(true); + preview->set_normal_texture(get_icon("GuiVisibilityHidden", "EditorIcons")); + preview->set_pressed_texture(get_icon("GuiVisibilityVisible", "EditorIcons")); + preview->set_v_size_flags(SIZE_SHRINK_CENTER); + + if (vsnode->get_output_port_for_preview() == i) { + preview->set_pressed(true); + } + + preview->connect("pressed", this, "_preview_select_port", varray(nodes[n_i], i), CONNECT_DEFERRED); + hb->add_child(preview); + } + + node->add_child(hb); + + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], valid_right, port_right, type_color[port_right]); + } + + if (vsnode->get_output_port_for_preview() >= 0) { + VisualShaderNodePortPreview *port_preview = memnew(VisualShaderNodePortPreview); + port_preview->setup(visual_shader, type, nodes[n_i], vsnode->get_output_port_for_preview()); + port_preview->set_h_size_flags(SIZE_SHRINK_CENTER); + node->add_child(port_preview); + } + + String error = vsnode->get_warning(visual_shader->get_mode(), type); + if (error != String()) { + Label *error_label = memnew(Label); + error_label->add_color_override("font_color", get_color("error_color", "Editor")); + error_label->set_text(error); + node->add_child(error_label); + } + } + + for (List<VisualShader::Connection>::Element *E = connections.front(); E; E = E->next()) { + + int from = E->get().from_node; + int from_idx = E->get().from_port; + int to = E->get().to_node; + int to_idx = E->get().to_port; + + graph->connect_node(itos(from), from_idx, itos(to), to_idx); + } +} + +void VisualShaderEditor::_preview_select_port(int p_node, int p_port) { + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + Ref<VisualShaderNode> node = visual_shader->get_node(type, p_node); + if (node.is_null()) { + return; + } + + if (node->get_output_port_for_preview() == p_port) { + p_port = -1; //toggle it + } + undo_redo->create_action("Set Uniform Name"); + undo_redo->add_do_method(node.ptr(), "set_output_port_for_preview", p_port); + undo_redo->add_undo_method(node.ptr(), "set_output_port_for_preview", node->get_output_port_for_preview()); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); +} + +void VisualShaderEditor::_line_edit_changed(const String &p_text, Object *line_edit, int p_node_id) { + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + Ref<VisualShaderNodeUniform> node = visual_shader->get_node(type, p_node_id); + ERR_FAIL_COND(!node.is_valid()); + + String validated_name = visual_shader->validate_uniform_name(p_text, node); + + updating = true; + undo_redo->create_action("Set Uniform Name"); + undo_redo->add_do_method(node.ptr(), "set_uniform_name", validated_name); + undo_redo->add_undo_method(node.ptr(), "set_uniform_name", node->get_uniform_name()); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + updating = false; + + Object::cast_to<LineEdit>(line_edit)->set_text(validated_name); +} + +void VisualShaderEditor::_line_edit_focus_out(Object *line_edit, int p_node_id) { + + String text = Object::cast_to<LineEdit>(line_edit)->get_text(); + _line_edit_changed(text, line_edit, p_node_id); +} + +void VisualShaderEditor::_port_edited() { + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + Variant value = property_editor->get_variant(); + Ref<VisualShaderNode> vsn = visual_shader->get_node(type, editing_node); + ERR_FAIL_COND(!vsn.is_valid()); + + undo_redo->create_action("Set Input Default Port"); + undo_redo->add_do_method(vsn.ptr(), "set_input_port_default_value", editing_port, value); + undo_redo->add_undo_method(vsn.ptr(), "set_input_port_default_value", editing_port, vsn->get_input_port_default_value(editing_port)); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + + property_editor->hide(); +} + +void VisualShaderEditor::_edit_port_default_input(Object *p_button, int p_node, int p_port) { + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + Ref<VisualShaderNode> vsn = visual_shader->get_node(type, p_node); + + Button *button = Object::cast_to<Button>(p_button); + ERR_FAIL_COND(!button); + Variant value = vsn->get_input_port_default_value(p_port); + property_editor->set_global_position(button->get_global_position() + Vector2(0, button->get_size().height)); + property_editor->edit(NULL, "", value.get_type(), value, 0, ""); + property_editor->popup(); + editing_node = p_node; + editing_port = p_port; +} + +void VisualShaderEditor::_add_node(int p_idx) { + + ERR_FAIL_INDEX(p_idx, add_options.size()); + + Ref<VisualShaderNode> vsnode; + + if (add_options[p_idx].type != String()) { + VisualShaderNode *vsn = Object::cast_to<VisualShaderNode>(ClassDB::instance(add_options[p_idx].type)); + ERR_FAIL_COND(!vsn); + vsnode = Ref<VisualShaderNode>(vsn); + } else { + ERR_FAIL_COND(add_options[p_idx].script.is_null()); + String base_type = add_options[p_idx].script->get_instance_base_type(); + VisualShaderNode *vsn = Object::cast_to<VisualShaderNode>(ClassDB::instance(base_type)); + ERR_FAIL_COND(!vsn); + vsnode = Ref<VisualShaderNode>(vsn); + vsnode->set_script(add_options[p_idx].script.get_ref_ptr()); + } + + Point2 position = (graph->get_scroll_ofs() + graph->get_size() * 0.5) / EDSCALE; + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + int id_to_use = visual_shader->get_valid_node_id(type); + + undo_redo->create_action("Add Node to Visual Shader"); + undo_redo->add_do_method(visual_shader.ptr(), "add_node", type, vsnode, position, id_to_use); + undo_redo->add_undo_method(visual_shader.ptr(), "remove_node", type, id_to_use); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); +} + +void VisualShaderEditor::_node_dragged(const Vector2 &p_from, const Vector2 &p_to, int p_node) { + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + updating = true; + undo_redo->create_action("Node Moved"); + undo_redo->add_do_method(visual_shader.ptr(), "set_node_position", type, p_node, p_to); + undo_redo->add_undo_method(visual_shader.ptr(), "set_node_position", type, p_node, 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 VisualShaderEditor::_connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index) { + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + int from = p_from.to_int(); + int to = p_to.to_int(); + + if (!visual_shader->can_connect_nodes(type, from, p_from_index, to, p_to_index)) { + 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(visual_shader.ptr(), "connect_nodes", type, from, p_from_index, to, p_to_index); + undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from, p_from_index, to, p_to_index); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); +} + +void VisualShaderEditor::_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); + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + int from = p_from.to_int(); + int to = p_to.to_int(); + + //updating = true; seems graph edit can handle this, no need to protect + undo_redo->create_action("Nodes Disconnected"); + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from, p_from_index, to, p_to_index); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, from, p_from_index, to, p_to_index); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + //updating = false; +} + +void VisualShaderEditor::_connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position) { +} + +void VisualShaderEditor::_delete_request(int which) { + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + undo_redo->create_action("Delete Node"); + undo_redo->add_do_method(visual_shader.ptr(), "remove_node", type, which); + undo_redo->add_undo_method(visual_shader.ptr(), "add_node", type, visual_shader->get_node(type, which), visual_shader->get_node_position(type, which), which); + + List<VisualShader::Connection> conns; + visual_shader->get_node_connections(type, &conns); + + for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) { + if (E->get().from_node == which || E->get().to_node == which) { + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port); + } + } + + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); +} + +void VisualShaderEditor::_node_selected(Object *p_node) { + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + GraphNode *gn = Object::cast_to<GraphNode>(p_node); + ERR_FAIL_COND(!gn); + + int id = String(gn->get_name()).to_int(); + + Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, id); + ERR_FAIL_COND(!vsnode.is_valid()); + + //do not rely on this, makes editor more complex + //EditorNode::get_singleton()->push_item(vsnode.ptr(), "", true); +} + +void VisualShaderEditor::_input(const Ref<InputEvent> p_event) { + if (graph->has_focus()) { + Ref<InputEventMouseButton> mb = p_event; + + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) { + add_node->get_popup()->set_position(get_viewport()->get_mouse_position()); + add_node->get_popup()->show_modal(); + } + } +} + +void VisualShaderEditor::_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")); + } + + if (p_what == NOTIFICATION_PROCESS) { + } +} + +void VisualShaderEditor::_scroll_changed(const Vector2 &p_scroll) { + if (updating) + return; + updating = true; + visual_shader->set_graph_offset(p_scroll / EDSCALE); + updating = false; +} + +void VisualShaderEditor::_node_changed(int p_id) { + if (updating) + return; + + if (is_visible_in_tree()) { + _update_graph(); + } +} + +void VisualShaderEditor::_duplicate_nodes() { + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + List<int> nodes; + + for (int i = 0; i < graph->get_child_count(); i++) { + + if (Object::cast_to<GraphNode>(graph->get_child(i))) { + int id = String(graph->get_child(i)->get_name()).to_int(); + Ref<VisualShaderNode> node = visual_shader->get_node(type, id); + Ref<VisualShaderNodeOutput> output = node; + if (output.is_valid()) //cant duplicate output + continue; + if (node.is_valid()) { + nodes.push_back(id); + } + } + } + + if (nodes.empty()) + return; + + undo_redo->create_action("Duplicate Nodes"); + + int base_id = visual_shader->get_valid_node_id(type); + int id_from = base_id; + Map<int, int> connection_remap; + + for (List<int>::Element *E = nodes.front(); E; E = E->next()) { + + connection_remap[E->get()] = id_from; + Ref<VisualShaderNode> node = visual_shader->get_node(type, E->get()); + + Ref<VisualShaderNode> dupli = node->duplicate(); + + undo_redo->add_do_method(visual_shader.ptr(), "add_node", type, dupli, visual_shader->get_node_position(type, E->get()) + Vector2(10, 10) * EDSCALE, id_from); + undo_redo->add_undo_method(visual_shader.ptr(), "remove_node", type, id_from); + + id_from++; + } + + List<VisualShader::Connection> conns; + visual_shader->get_node_connections(type, &conns); + + for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) { + if (connection_remap.has(E->get().from_node) && connection_remap.has(E->get().to_node)) { + undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, connection_remap[E->get().from_node], E->get().from_port, connection_remap[E->get().to_node], E->get().to_port); + } + } + + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + + //reselect + for (int i = 0; i < graph->get_child_count(); i++) { + + if (Object::cast_to<GraphNode>(graph->get_child(i))) { + int id = String(graph->get_child(i)->get_name()).to_int(); + if (nodes.find(id)) { + Object::cast_to<GraphNode>(graph->get_child(i))->set_selected(true); + } else { + Object::cast_to<GraphNode>(graph->get_child(i))->set_selected(false); + } + } + } +} + +void VisualShaderEditor::_mode_selected(int p_id) { + _update_graph(); +} + +void VisualShaderEditor::_input_select_item(Ref<VisualShaderNodeInput> input, String name) { + + String prev_name = input->get_input_name(); + + if (name == prev_name) + return; + + bool type_changed = input->get_input_type_by_name(name) != input->get_input_type_by_name(prev_name); + + UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + undo_redo->create_action("Visual Shader Input Type Changed"); + + undo_redo->add_do_method(input.ptr(), "set_input_name", name); + undo_redo->add_undo_method(input.ptr(), "set_input_name", prev_name); + + if (type_changed) { + //restore connections if type changed + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + int id = visual_shader->find_node_id(type, input); + List<VisualShader::Connection> conns; + visual_shader->get_node_connections(type, &conns); + for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) { + if (E->get().from_node == id) { + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port); + } + } + } + + undo_redo->add_do_method(VisualShaderEditor::get_singleton(), "_update_graph"); + undo_redo->add_undo_method(VisualShaderEditor::get_singleton(), "_update_graph"); + + undo_redo->commit_action(); +} + +void VisualShaderEditor::_bind_methods() { + + ClassDB::bind_method("_update_graph", &VisualShaderEditor::_update_graph); + ClassDB::bind_method("_add_node", &VisualShaderEditor::_add_node); + ClassDB::bind_method("_node_dragged", &VisualShaderEditor::_node_dragged); + ClassDB::bind_method("_connection_request", &VisualShaderEditor::_connection_request); + ClassDB::bind_method("_disconnection_request", &VisualShaderEditor::_disconnection_request); + ClassDB::bind_method("_node_selected", &VisualShaderEditor::_node_selected); + ClassDB::bind_method("_scroll_changed", &VisualShaderEditor::_scroll_changed); + ClassDB::bind_method("_delete_request", &VisualShaderEditor::_delete_request); + ClassDB::bind_method("_node_changed", &VisualShaderEditor::_node_changed); + ClassDB::bind_method("_edit_port_default_input", &VisualShaderEditor::_edit_port_default_input); + ClassDB::bind_method("_port_edited", &VisualShaderEditor::_port_edited); + ClassDB::bind_method("_connection_to_empty", &VisualShaderEditor::_connection_to_empty); + ClassDB::bind_method("_line_edit_focus_out", &VisualShaderEditor::_line_edit_focus_out); + ClassDB::bind_method("_line_edit_changed", &VisualShaderEditor::_line_edit_changed); + ClassDB::bind_method("_duplicate_nodes", &VisualShaderEditor::_duplicate_nodes); + ClassDB::bind_method("_mode_selected", &VisualShaderEditor::_mode_selected); + ClassDB::bind_method("_input_select_item", &VisualShaderEditor::_input_select_item); + ClassDB::bind_method("_preview_select_port", &VisualShaderEditor::_preview_select_port); + ClassDB::bind_method("_input", &VisualShaderEditor::_input); +} + +VisualShaderEditor *VisualShaderEditor::singleton = NULL; + +VisualShaderEditor::VisualShaderEditor() { + + singleton = this; + updating = false; + + graph = memnew(GraphEdit); + add_child(graph); + graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_SCALAR); + graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_VECTOR); + graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_TRANSFORM); + //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"); + graph->connect("duplicate_nodes_request", this, "_duplicate_nodes"); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_SCALAR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_VECTOR); + //graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_SCALAR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_VECTOR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_TRANSFORM, VisualShaderNode::PORT_TYPE_TRANSFORM); + + VSeparator *vs = memnew(VSeparator); + graph->get_zoom_hbox()->add_child(vs); + graph->get_zoom_hbox()->move_child(vs, 0); + + edit_type = memnew(OptionButton); + edit_type->add_item(TTR("Vertex")); + edit_type->add_item(TTR("Fragment")); + edit_type->add_item(TTR("Light")); + edit_type->select(1); + edit_type->connect("item_selected", this, "_mode_selected"); + graph->get_zoom_hbox()->add_child(edit_type); + graph->get_zoom_hbox()->move_child(edit_type, 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("id_pressed", this, "_add_node"); + + add_options.push_back(AddOption("Scalar", "Constants", "VisualShaderNodeScalarConstant")); + add_options.push_back(AddOption("Vector", "Constants", "VisualShaderNodeVec3Constant")); + add_options.push_back(AddOption("Color", "Constants", "VisualShaderNodeColorConstant")); + add_options.push_back(AddOption("Transform", "Constants", "VisualShaderNodeTransformConstant")); + add_options.push_back(AddOption("Texture", "Constants", "VisualShaderNodeTexture")); + add_options.push_back(AddOption("CubeMap", "Constants", "VisualShaderNodeCubeMap")); + add_options.push_back(AddOption("ScalarOp", "Operators", "VisualShaderNodeScalarOp")); + add_options.push_back(AddOption("VectorOp", "Operators", "VisualShaderNodeVectorOp")); + add_options.push_back(AddOption("ColorOp", "Operators", "VisualShaderNodeColorOp")); + add_options.push_back(AddOption("TransformMult", "Operators", "VisualShaderNodeTransformMult")); + add_options.push_back(AddOption("TransformVectorMult", "Operators", "VisualShaderNodeTransformVecMult")); + add_options.push_back(AddOption("ScalarFunc", "Functions", "VisualShaderNodeScalarFunc")); + add_options.push_back(AddOption("VectorFunc", "Functions", "VisualShaderNodeVectorFunc")); + add_options.push_back(AddOption("DotProduct", "Functions", "VisualShaderNodeDotProduct")); + add_options.push_back(AddOption("VectorLen", "Functions", "VisualShaderNodeVectorLen")); + add_options.push_back(AddOption("ScalarInterp", "Interpolation", "VisualShaderNodeScalarInterp")); + add_options.push_back(AddOption("VectorInterp", "Interpolation", "VisualShaderNodeVectorInterp")); + add_options.push_back(AddOption("VectorCompose", "Compose", "VisualShaderNodeVectorCompose")); + add_options.push_back(AddOption("TransformCompose", "Compose", "VisualShaderNodeTransformCompose")); + add_options.push_back(AddOption("VectorDecompose", "Decompose", "VisualShaderNodeVectorDecompose")); + add_options.push_back(AddOption("TransformDecompose", "Decompose", "VisualShaderNodeTransformDecompose")); + add_options.push_back(AddOption("Scalar", "Uniforms", "VisualShaderNodeScalarUniform")); + add_options.push_back(AddOption("Vector", "Uniforms", "VisualShaderNodeVec3Uniform")); + add_options.push_back(AddOption("Color", "Uniforms", "VisualShaderNodeColorUniform")); + add_options.push_back(AddOption("Transform", "Uniforms", "VisualShaderNodeTransformUniform")); + add_options.push_back(AddOption("Texture", "Uniforms", "VisualShaderNodeTextureUniform")); + add_options.push_back(AddOption("CubeMap", "Uniforms", "VisualShaderNodeCubeMapUniform")); + add_options.push_back(AddOption("Input", "Inputs", "VisualShaderNodeInput")); + + _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"); + error_panel->hide(); + + undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + Ref<VisualShaderNodePluginDefault> default_plugin; + default_plugin.instance(); + add_plugin(default_plugin); + + property_editor = memnew(CustomPropertyEditor); + add_child(property_editor); + + property_editor->connect("variant_changed", this, "_port_edited"); +} + +void VisualShaderEditorPlugin::edit(Object *p_object) { + + visual_shader_editor->edit(Object::cast_to<VisualShader>(p_object)); +} + +bool VisualShaderEditorPlugin::handles(Object *p_object) const { + + return p_object->is_class("VisualShader"); +} + +void VisualShaderEditorPlugin::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(visual_shader_editor); + visual_shader_editor->set_process_input(true); + //visual_shader_editor->set_process(true); + } else { + + if (visual_shader_editor->is_visible_in_tree()) + editor->hide_bottom_panel(); + button->hide(); + visual_shader_editor->set_process_input(false); + //visual_shader_editor->set_process(false); + } +} + +VisualShaderEditorPlugin::VisualShaderEditorPlugin(EditorNode *p_node) { + + editor = p_node; + visual_shader_editor = memnew(VisualShaderEditor); + visual_shader_editor->set_custom_minimum_size(Size2(0, 300)); + + button = editor->add_bottom_panel_item(TTR("VisualShader"), visual_shader_editor); + button->hide(); +} + +VisualShaderEditorPlugin::~VisualShaderEditorPlugin() { +} + +//////////////// + +class VisualShaderNodePluginInputEditor : public OptionButton { + GDCLASS(VisualShaderNodePluginInputEditor, OptionButton) + + Ref<VisualShaderNodeInput> input; + +protected: + static void _bind_methods() { + ClassDB::bind_method("_item_selected", &VisualShaderNodePluginInputEditor::_item_selected); + } + +public: + void _notification(int p_what) { + if (p_what == NOTIFICATION_READY) { + connect("item_selected", this, "_item_selected"); + } + } + + void _item_selected(int p_item) { + VisualShaderEditor::get_singleton()->call_deferred("_input_select_item", input, get_item_text(p_item)); + } + + void setup(const Ref<VisualShaderNodeInput> &p_input) { + input = p_input; + Ref<Texture> type_icon[3] = { + EditorNode::get_singleton()->get_gui_base()->get_icon("float", "EditorIcons"), + EditorNode::get_singleton()->get_gui_base()->get_icon("Vector3", "EditorIcons"), + EditorNode::get_singleton()->get_gui_base()->get_icon("Transform", "EditorIcons"), + }; + + add_item("[None]"); + int to_select = -1; + for (int i = 0; i < input->get_input_index_count(); i++) { + if (input->get_input_name() == input->get_input_index_name(i)) { + to_select = i + 1; + } + add_icon_item(type_icon[input->get_input_index_type(i)], input->get_input_index_name(i)); + } + + if (to_select >= 0) { + select(to_select); + } + } +}; + +class VisualShaderNodePluginDefaultEditor : public VBoxContainer { + GDCLASS(VisualShaderNodePluginDefaultEditor, VBoxContainer) +public: + void _property_changed(const String &prop, const Variant &p_value) { + + UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + updating = true; + undo_redo->create_action("Edit Visual Property: " + prop, UndoRedo::MERGE_ENDS); + undo_redo->add_do_property(node.ptr(), prop, p_value); + undo_redo->add_undo_property(node.ptr(), prop, node->get(prop)); + undo_redo->commit_action(); + updating = false; + } + + void _node_changed() { + if (updating) + return; + for (int i = 0; i < properties.size(); i++) { + properties[i]->update_property(); + } + } + + void _refresh_request() { + VisualShaderEditor::get_singleton()->call_deferred("_update_graph"); + } + + bool updating; + Ref<VisualShaderNode> node; + Vector<EditorProperty *> properties; + + void setup(Vector<EditorProperty *> p_properties, const Vector<StringName> &p_names, Ref<VisualShaderNode> p_node) { + updating = false; + node = p_node; + properties = p_properties; + + for (int i = 0; i < p_properties.size(); i++) { + + add_child(p_properties[i]); + + properties[i]->connect("property_changed", this, "_property_changed"); + properties[i]->set_object_and_property(node.ptr(), p_names[i]); + properties[i]->update_property(); + properties[i]->set_name_split_ratio(0); + } + node->connect("changed", this, "_node_changed"); + node->connect("editor_refresh_request", this, "_refresh_request", varray(), CONNECT_DEFERRED); + } + + static void _bind_methods() { + ClassDB::bind_method("_property_changed", &VisualShaderNodePluginDefaultEditor::_property_changed); + ClassDB::bind_method("_node_changed", &VisualShaderNodePluginDefaultEditor::_node_changed); + ClassDB::bind_method("_refresh_request", &VisualShaderNodePluginDefaultEditor::_refresh_request); + } +}; + +Control *VisualShaderNodePluginDefault::create_editor(const Ref<VisualShaderNode> &p_node) { + + if (p_node->is_class("VisualShaderNodeInput")) { + //create input + VisualShaderNodePluginInputEditor *input_editor = memnew(VisualShaderNodePluginInputEditor); + input_editor->setup(p_node); + return input_editor; + } + + Vector<StringName> properties = p_node->get_editable_properties(); + if (properties.size() == 0) { + return NULL; + } + + List<PropertyInfo> props; + p_node->get_property_list(&props); + + Vector<PropertyInfo> pinfo; + + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + + for (int i = 0; i < properties.size(); i++) { + if (E->get().name == String(properties[i])) { + pinfo.push_back(E->get()); + } + } + } + + if (pinfo.size() == 0) + return NULL; + + properties.clear(); + + Ref<VisualShaderNode> node = p_node; + Vector<EditorProperty *> editors; + + for (int i = 0; i < pinfo.size(); i++) { + + EditorProperty *prop = EditorInspector::instantiate_property_editor(node.ptr(), pinfo[i].type, pinfo[i].name, pinfo[i].hint, pinfo[i].hint_string, pinfo[i].usage); + if (!prop) + return NULL; + + if (Object::cast_to<EditorPropertyResource>(prop)) { + Object::cast_to<EditorPropertyResource>(prop)->set_use_sub_inspector(false); + prop->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); + } else if (Object::cast_to<EditorPropertyTransform>(prop)) { + prop->set_custom_minimum_size(Size2(250 * EDSCALE, 0)); + } else if (Object::cast_to<EditorPropertyFloat>(prop) || Object::cast_to<EditorPropertyVector3>(prop)) { + prop->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); + } else if (Object::cast_to<EditorPropertyEnum>(prop)) { + prop->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); + Object::cast_to<EditorPropertyEnum>(prop)->set_option_button_clip(false); + } + + editors.push_back(prop); + properties.push_back(pinfo[i].name); + } + VisualShaderNodePluginDefaultEditor *editor = memnew(VisualShaderNodePluginDefaultEditor); + editor->setup(editors, properties, p_node); + return editor; +} + +void EditorPropertyShaderMode::_option_selected(int p_which) { + + //will not use this, instead will do all the logic setting manually + //emit_signal("property_changed", get_edited_property(), p_which); + + Ref<VisualShader> visual_shader(Object::cast_to<VisualShader>(get_edited_object())); + + if (visual_shader->get_mode() == p_which) + return; + + UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + undo_redo->create_action("Visual Shader Mode Changed"); + //do is easy + undo_redo->add_do_method(visual_shader.ptr(), "set_mode", p_which); + undo_redo->add_undo_method(visual_shader.ptr(), "set_mode", visual_shader->get_mode()); + //now undo is hell + + //1. restore connections to output + for (int i = 0; i < VisualShader::TYPE_MAX; i++) { + + VisualShader::Type type = VisualShader::Type(i); + List<VisualShader::Connection> conns; + visual_shader->get_node_connections(type, &conns); + for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) { + if (E->get().to_node == VisualShader::NODE_ID_OUTPUT) { + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port); + } + } + } + //2. restore input indices + for (int i = 0; i < VisualShader::TYPE_MAX; i++) { + + VisualShader::Type type = VisualShader::Type(i); + Vector<int> nodes = visual_shader->get_node_list(type); + for (int i = 0; i < nodes.size(); i++) { + Ref<VisualShaderNodeInput> input = visual_shader->get_node(type, nodes[i]); + if (!input.is_valid()) { + continue; + } + + undo_redo->add_undo_method(input.ptr(), "set_input_name", input->get_input_name()); + } + } + + //3. restore enums and flags + List<PropertyInfo> props; + visual_shader->get_property_list(&props); + + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + + if (E->get().name.begins_with("flags/") || E->get().name.begins_with("modes/")) { + undo_redo->add_undo_property(visual_shader.ptr(), E->get().name, visual_shader->get(E->get().name)); + } + } + + //update graph + undo_redo->add_do_method(VisualShaderEditor::get_singleton(), "_update_graph"); + undo_redo->add_undo_method(VisualShaderEditor::get_singleton(), "_update_graph"); + + undo_redo->commit_action(); +} + +void EditorPropertyShaderMode::update_property() { + + int which = get_edited_object()->get(get_edited_property()); + options->select(which); +} + +void EditorPropertyShaderMode::setup(const Vector<String> &p_options) { + for (int i = 0; i < p_options.size(); i++) { + options->add_item(p_options[i], i); + } +} + +void EditorPropertyShaderMode::set_option_button_clip(bool p_enable) { + options->set_clip_text(p_enable); +} + +void EditorPropertyShaderMode::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_option_selected"), &EditorPropertyShaderMode::_option_selected); +} + +EditorPropertyShaderMode::EditorPropertyShaderMode() { + options = memnew(OptionButton); + options->set_clip_text(true); + add_child(options); + add_focusable(options); + options->connect("item_selected", this, "_option_selected"); +} + +bool EditorInspectorShaderModePlugin::can_handle(Object *p_object) { + return true; //can handle everything +} + +void EditorInspectorShaderModePlugin::parse_begin(Object *p_object) { + //do none +} + +bool EditorInspectorShaderModePlugin::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 == "mode" && p_object->is_class("VisualShader") && p_type == Variant::INT) { + + EditorPropertyShaderMode *editor = memnew(EditorPropertyShaderMode); + Vector<String> options = p_hint_text.split(","); + editor->setup(options); + add_property_editor(p_path, editor); + + return true; + } + + return false; //can be overriden, although it will most likely be last anyway +} + +void EditorInspectorShaderModePlugin::parse_end() { + //do none +} +////////////////////////////////// + +void VisualShaderNodePortPreview::_shader_changed() { + if (shader.is_null()) { + return; + } + + Vector<VisualShader::DefaultTextureParam> default_textures; + String shader_code = shader->generate_preview_shader(type, node, port, default_textures); + + Ref<Shader> preview_shader; + preview_shader.instance(); + preview_shader->set_code(shader_code); + for (int i = 0; i < default_textures.size(); i++) { + preview_shader->set_default_texture_param(default_textures[i].name, default_textures[i].param); + } + + Ref<ShaderMaterial> material; + material.instance(); + material->set_shader(preview_shader); + + //find if a material is also being edited and copy parameters to this one + + for (int i = EditorNode::get_singleton()->get_editor_history()->get_path_size() - 1; i >= 0; i--) { + Object *object = ObjectDB::get_instance(EditorNode::get_singleton()->get_editor_history()->get_path_object(i)); + if (!object) + continue; + ShaderMaterial *src_mat = Object::cast_to<ShaderMaterial>(object); + if (src_mat && src_mat->get_shader().is_valid()) { + + List<PropertyInfo> params; + src_mat->get_shader()->get_param_list(¶ms); + for (List<PropertyInfo>::Element *E = params.front(); E; E = E->next()) { + material->set(E->get().name, src_mat->get(E->get().name)); + } + } + } + + set_material(material); +} + +void VisualShaderNodePortPreview::setup(const Ref<VisualShader> &p_shader, VisualShader::Type p_type, int p_node, int p_port) { + + shader = p_shader; + shader->connect("changed", this, "_shader_changed"); + type = p_type; + port = p_port; + node = p_node; + update(); + _shader_changed(); +} + +Size2 VisualShaderNodePortPreview::get_minimum_size() const { + return Size2(100, 100) * EDSCALE; +} + +void VisualShaderNodePortPreview::_notification(int p_what) { + if (p_what == NOTIFICATION_DRAW) { + Vector<Vector2> points; + Vector<Vector2> uvs; + Vector<Color> colors; + points.push_back(Vector2()); + uvs.push_back(Vector2(0, 0)); + colors.push_back(Color(1, 1, 1, 1)); + points.push_back(Vector2(get_size().width, 0)); + uvs.push_back(Vector2(1, 0)); + colors.push_back(Color(1, 1, 1, 1)); + points.push_back(get_size()); + uvs.push_back(Vector2(1, 1)); + colors.push_back(Color(1, 1, 1, 1)); + points.push_back(Vector2(0, get_size().height)); + uvs.push_back(Vector2(0, 1)); + colors.push_back(Color(1, 1, 1, 1)); + + draw_primitive(points, colors, uvs); + } +} + +void VisualShaderNodePortPreview::_bind_methods() { + ClassDB::bind_method("_shader_changed", &VisualShaderNodePortPreview::_shader_changed); +} + +VisualShaderNodePortPreview::VisualShaderNodePortPreview() { +} diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h new file mode 100644 index 0000000000..f86374ff6b --- /dev/null +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -0,0 +1,187 @@ +#ifndef VISUAL_SHADER_EDITOR_PLUGIN_H +#define VISUAL_SHADER_EDITOR_PLUGIN_H + +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" +#include "editor/property_editor.h" +#include "scene/gui/button.h" +#include "scene/gui/graph_edit.h" +#include "scene/gui/popup.h" +#include "scene/gui/tree.h" +#include "scene/resources/visual_shader.h" + +class VisualShaderNodePlugin : public Reference { + + GDCLASS(VisualShaderNodePlugin, Reference) +protected: + static void _bind_methods(); + +public: + virtual Control *create_editor(const Ref<VisualShaderNode> &p_node); +}; + +class VisualShaderEditor : public VBoxContainer { + + GDCLASS(VisualShaderEditor, VBoxContainer); + + CustomPropertyEditor *property_editor; + int editing_node; + int editing_port; + + Ref<VisualShader> visual_shader; + GraphEdit *graph; + MenuButton *add_node; + + OptionButton *edit_type; + + PanelContainer *error_panel; + Label *error_label; + + UndoRedo *undo_redo; + + void _update_graph(); + + struct AddOption { + String name; + String category; + String type; + Ref<Script> script; + AddOption(const String &p_name = String(), const String &p_category = String(), const String &p_type = String()) { + name = p_name; + type = p_type; + category = p_category; + } + }; + + Vector<AddOption> add_options; + + void _draw_color_over_button(Object *obj, Color p_color); + + void _add_node(int p_idx); + void _update_options_menu(); + + static VisualShaderEditor *singleton; + + void _node_dragged(const Vector2 &p_from, const Vector2 &p_to, int 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 _delete_request(int); + + void _removed_from_graph(); + + void _node_changed(int p_id); + + void _edit_port_default_input(Object *p_button, int p_node, int p_port); + void _port_edited(); + + void _connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position); + + void _line_edit_changed(const String &p_text, Object *line_edit, int p_node_id); + void _line_edit_focus_out(Object *line_edit, int p_node_id); + + void _duplicate_nodes(); + + Vector<Ref<VisualShaderNodePlugin> > plugins; + + void _mode_selected(int p_id); + + void _input_select_item(Ref<VisualShaderNodeInput> input, String name); + + void _preview_select_port(int p_node, int p_port); + void _input(const Ref<InputEvent> p_event); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void add_plugin(const Ref<VisualShaderNodePlugin> &p_plugin); + void remove_plugin(const Ref<VisualShaderNodePlugin> &p_plugin); + + static VisualShaderEditor *get_singleton() { return singleton; } + + void add_custom_type(const String &p_name, const String &p_category, const Ref<Script> &p_script); + void remove_custom_type(const Ref<Script> &p_script); + + virtual Size2 get_minimum_size() const; + void edit(VisualShader *p_visual_shader); + VisualShaderEditor(); +}; + +class VisualShaderEditorPlugin : public EditorPlugin { + + GDCLASS(VisualShaderEditorPlugin, EditorPlugin); + + VisualShaderEditor *visual_shader_editor; + EditorNode *editor; + Button *button; + +public: + virtual String get_name() const { return "VisualShader"; } + 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); + + VisualShaderEditorPlugin(EditorNode *p_node); + ~VisualShaderEditorPlugin(); +}; + +class VisualShaderNodePluginDefault : public VisualShaderNodePlugin { + + GDCLASS(VisualShaderNodePluginDefault, VisualShaderNodePlugin) + +public: + virtual Control *create_editor(const Ref<VisualShaderNode> &p_node); +}; + +class EditorPropertyShaderMode : public EditorProperty { + GDCLASS(EditorPropertyShaderMode, EditorProperty) + OptionButton *options; + + void _option_selected(int p_which); + +protected: + static void _bind_methods(); + +public: + void setup(const Vector<String> &p_options); + virtual void update_property(); + void set_option_button_clip(bool p_enable); + EditorPropertyShaderMode(); +}; + +class EditorInspectorShaderModePlugin : public EditorInspectorPlugin { + GDCLASS(EditorInspectorShaderModePlugin, 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(); +}; + +class VisualShaderNodePortPreview : public Control { + GDCLASS(VisualShaderNodePortPreview, Control) + Ref<VisualShader> shader; + VisualShader::Type type; + int node; + int port; + void _shader_changed(); //must regen +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + virtual Size2 get_minimum_size() const; + void setup(const Ref<VisualShader> &p_shader, VisualShader::Type p_type, int p_node, int p_port); + VisualShaderNodePortPreview(); +}; + +#endif // VISUAL_SHADER_EDITOR_PLUGIN_H |