Add drag and drop.

Rearrange tasks with drag and drop in editor.
This commit is contained in:
Serhii Snitsaruk 2022-09-03 17:00:20 +02:00
parent 29fab3a8d8
commit aefa132290
4 changed files with 117 additions and 4 deletions

View File

@ -184,6 +184,17 @@ bool BTTask::has_child(const Ref<BTTask> &p_child) const {
return children.find(p_child) != -1; return children.find(p_child) != -1;
} }
bool BTTask::is_descendant_of(const Ref<BTTask> &p_task) const {
const BTTask *task = this;
while (task != nullptr) {
task = task->parent;
if (task == p_task.ptr()) {
return true;
}
}
return false;
}
int BTTask::get_child_index(const Ref<BTTask> &p_child) const { int BTTask::get_child_index(const Ref<BTTask> &p_child) const {
return children.find(p_child); return children.find(p_child);
} }
@ -262,6 +273,7 @@ void BTTask::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_child_at_index", "p_child", "p_idx"), &BTTask::add_child_at_index); ClassDB::bind_method(D_METHOD("add_child_at_index", "p_child", "p_idx"), &BTTask::add_child_at_index);
ClassDB::bind_method(D_METHOD("remove_child", "p_child"), &BTTask::remove_child); ClassDB::bind_method(D_METHOD("remove_child", "p_child"), &BTTask::remove_child);
ClassDB::bind_method(D_METHOD("has_child", "p_child"), &BTTask::has_child); ClassDB::bind_method(D_METHOD("has_child", "p_child"), &BTTask::has_child);
ClassDB::bind_method(D_METHOD("is_descendant_of", "p_task"), &BTTask::is_descendant_of);
ClassDB::bind_method(D_METHOD("get_child_index", "p_child"), &BTTask::get_child_index); ClassDB::bind_method(D_METHOD("get_child_index", "p_child"), &BTTask::get_child_index);
ClassDB::bind_method(D_METHOD("next_sibling"), &BTTask::next_sibling); ClassDB::bind_method(D_METHOD("next_sibling"), &BTTask::next_sibling);
ClassDB::bind_method(D_METHOD("print_tree", "p_initial_tabs"), &BTTask::print_tree, Variant(0)); ClassDB::bind_method(D_METHOD("print_tree", "p_initial_tabs"), &BTTask::print_tree, Variant(0));

View File

@ -66,6 +66,7 @@ public:
void add_child_at_index(Ref<BTTask> p_child, int p_idx); void add_child_at_index(Ref<BTTask> p_child, int p_idx);
void remove_child(Ref<BTTask> p_child); void remove_child(Ref<BTTask> p_child);
bool has_child(const Ref<BTTask> &p_child) const; bool has_child(const Ref<BTTask> &p_child) const;
bool is_descendant_of(const Ref<BTTask> &p_task) const;
int get_child_index(const Ref<BTTask> &p_child) const; int get_child_index(const Ref<BTTask> &p_child) const;
Ref<BTTask> next_sibling() const; Ref<BTTask> next_sibling() const;
virtual String get_configuration_warning() const; virtual String get_configuration_warning() const;

View File

@ -5,6 +5,7 @@
#include "limbo_ai_editor_plugin.h" #include "limbo_ai_editor_plugin.h"
#include "core/class_db.h" #include "core/class_db.h"
#include "core/dictionary.h"
#include "core/error_list.h" #include "core/error_list.h"
#include "core/error_macros.h" #include "core/error_macros.h"
#include "core/io/resource_loader.h" #include "core/io/resource_loader.h"
@ -43,6 +44,8 @@
#include "scene/gui/tree.h" #include "scene/gui/tree.h"
#include <cstddef> #include <cstddef>
////////////////////////////// TaskTree //////////////////////////////////////
TreeItem *TaskTree::_create_tree(const Ref<BTTask> &p_task, TreeItem *p_parent, int p_idx) { TreeItem *TaskTree::_create_tree(const Ref<BTTask> &p_task, TreeItem *p_parent, int p_idx) {
ERR_FAIL_COND_V(p_task.is_null(), nullptr); ERR_FAIL_COND_V(p_task.is_null(), nullptr);
TreeItem *item = tree->create_item(p_parent, p_idx); TreeItem *item = tree->create_item(p_parent, p_idx);
@ -153,6 +156,53 @@ void TaskTree::deselect() {
} }
} }
Variant TaskTree::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
if (editable && tree->get_item_at_position(p_point)) {
Dictionary drag_data;
drag_data["type"] = "task";
drag_data["task"] = tree->get_item_at_position(p_point)->get_metadata(0);
tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN | Tree::DROP_MODE_ON_ITEM);
return drag_data;
}
return Variant();
}
bool TaskTree::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
if (!editable) {
return false;
}
Dictionary d = p_data;
if (!d.has("type") || !d.has("task")) {
return false;
}
int section = tree->get_drop_section_at_position(p_point);
TreeItem *item = tree->get_item_at_position(p_point);
if (!item || section < -1 || (section == -1 && !item->get_parent())) {
return false;
}
if (String(d["type"]) == "task") {
Ref<BTTask> task = d["task"];
const Ref<BTTask> to_task = item->get_metadata(0);
if (task != to_task && !to_task->is_descendant_of(task)) {
return true;
}
}
return false;
}
void TaskTree::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
Dictionary d = p_data;
TreeItem *item = tree->get_item_at_position(p_point);
if (item && d.has("task")) {
Ref<BTTask> task = d["task"];
emit_signal("task_dragged", task, item->get_metadata(0), tree->get_drop_section_at_position(p_point));
}
}
void TaskTree::_bind_methods() { void TaskTree::_bind_methods() {
ClassDB::bind_method(D_METHOD("_on_item_rmb_selected"), &TaskTree::_on_item_rmb_selected); ClassDB::bind_method(D_METHOD("_on_item_rmb_selected"), &TaskTree::_on_item_rmb_selected);
ClassDB::bind_method(D_METHOD("_on_item_selected"), &TaskTree::_on_item_selected); ClassDB::bind_method(D_METHOD("_on_item_selected"), &TaskTree::_on_item_selected);
@ -163,11 +213,21 @@ void TaskTree::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_selected"), &TaskTree::get_selected); ClassDB::bind_method(D_METHOD("get_selected"), &TaskTree::get_selected);
ClassDB::bind_method(D_METHOD("deselect"), &TaskTree::deselect); ClassDB::bind_method(D_METHOD("deselect"), &TaskTree::deselect);
ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &TaskTree::get_drag_data_fw);
ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &TaskTree::can_drop_data_fw);
ClassDB::bind_method(D_METHOD("drop_data_fw"), &TaskTree::drop_data_fw);
ADD_SIGNAL(MethodInfo("rmb_pressed")); ADD_SIGNAL(MethodInfo("rmb_pressed"));
ADD_SIGNAL(MethodInfo("task_selected")); ADD_SIGNAL(MethodInfo("task_selected"));
ADD_SIGNAL(MethodInfo("task_dragged",
PropertyInfo(Variant::OBJECT, "p_task", PROPERTY_HINT_RESOURCE_TYPE, "BTTask"),
PropertyInfo(Variant::OBJECT, "p_to_task", PROPERTY_HINT_RESOURCE_TYPE, "BTTask"),
PropertyInfo(Variant::INT, "p_type")));
} }
TaskTree::TaskTree() { TaskTree::TaskTree() {
editable = true;
tree = memnew(Tree); tree = memnew(Tree);
add_child(tree); add_child(tree);
tree->set_columns(2); tree->set_columns(2);
@ -179,12 +239,15 @@ TaskTree::TaskTree() {
tree->set_allow_rmb_select(true); tree->set_allow_rmb_select(true);
tree->connect("item_rmb_selected", this, "_on_item_rmb_selected"); tree->connect("item_rmb_selected", this, "_on_item_rmb_selected");
tree->connect("item_selected", this, "_on_item_selected"); tree->connect("item_selected", this, "_on_item_selected");
tree->set_drag_forwarding(this);
} }
TaskTree::~TaskTree() { TaskTree::~TaskTree() {
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////// TaskTree //////////////////////////////////////
////////////////////////////// TaskSection ////////////////////////////////////
void TaskSection::_on_task_button_pressed(const StringName &p_task) { void TaskSection::_on_task_button_pressed(const StringName &p_task) {
emit_signal("task_button_pressed", p_task); emit_signal("task_button_pressed", p_task);
@ -242,7 +305,9 @@ TaskSection::TaskSection(String p_category_name, EditorNode *p_editor) {
TaskSection::~TaskSection() { TaskSection::~TaskSection() {
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////// TaskSection ////////////////////////////////////
////////////////////////////// TaskPanel /////////////////////////////////////
void TaskPanel::_on_task_button_pressed(const StringName &p_task) { void TaskPanel::_on_task_button_pressed(const StringName &p_task) {
emit_signal("task_selected", p_task); emit_signal("task_selected", p_task);
@ -375,7 +440,9 @@ TaskPanel::TaskPanel(EditorNode *p_editor) {
TaskPanel::~TaskPanel() { TaskPanel::~TaskPanel() {
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////// TaskPanel /////////////////////////////////////
//////////////////////////// LimboAIEditor ///////////////////////////////////
void LimboAIEditor::_add_task(const Ref<BTTask> &p_task) { void LimboAIEditor::_add_task(const Ref<BTTask> &p_task) {
ERR_FAIL_COND(p_task.is_null()); ERR_FAIL_COND(p_task.is_null());
@ -600,6 +667,28 @@ void LimboAIEditor::_on_history_forward() {
_edit_bt(history[idx_history]); _edit_bt(history[idx_history]);
} }
void LimboAIEditor::_on_task_dragged(Ref<BTTask> p_task, Ref<BTTask> p_to_task, int p_type) {
if (p_task == p_to_task) {
return;
}
if (p_type == 0) {
p_task->get_parent()->remove_child(p_task);
p_to_task->add_child(p_task);
task_tree->update_tree();
_mark_as_dirty(true);
} else if (p_type == -1 && p_to_task->get_parent().is_valid()) {
p_task->get_parent()->remove_child(p_task);
p_to_task->get_parent()->add_child_at_index(p_task, p_to_task->get_parent()->get_child_index(p_to_task));
task_tree->update_tree();
_mark_as_dirty(true);
} else if (p_type == 1 && p_to_task->get_parent().is_valid()) {
p_task->get_parent()->remove_child(p_task);
p_to_task->get_parent()->add_child_at_index(p_task, p_to_task->get_parent()->get_child_index(p_to_task) + 1);
task_tree->update_tree();
_mark_as_dirty(true);
}
}
void LimboAIEditor::apply_changes() { void LimboAIEditor::apply_changes() {
for (int i = 0; i < history.size(); i++) { for (int i = 0; i < history.size(); i++) {
Ref<BehaviorTree> bt = history.get(i); Ref<BehaviorTree> bt = history.get(i);
@ -624,6 +713,7 @@ void LimboAIEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_on_save_pressed"), &LimboAIEditor::_on_save_pressed); ClassDB::bind_method(D_METHOD("_on_save_pressed"), &LimboAIEditor::_on_save_pressed);
ClassDB::bind_method(D_METHOD("_on_history_back"), &LimboAIEditor::_on_history_back); ClassDB::bind_method(D_METHOD("_on_history_back"), &LimboAIEditor::_on_history_back);
ClassDB::bind_method(D_METHOD("_on_history_forward"), &LimboAIEditor::_on_history_forward); ClassDB::bind_method(D_METHOD("_on_history_forward"), &LimboAIEditor::_on_history_forward);
ClassDB::bind_method(D_METHOD("_on_task_dragged", "p_task", "p_to_task", "p_type"), &LimboAIEditor::_on_task_dragged);
ClassDB::bind_method(D_METHOD("_new_bt"), &LimboAIEditor::_new_bt); ClassDB::bind_method(D_METHOD("_new_bt"), &LimboAIEditor::_new_bt);
ClassDB::bind_method(D_METHOD("_save_bt", "p_path"), &LimboAIEditor::_save_bt); ClassDB::bind_method(D_METHOD("_save_bt", "p_path"), &LimboAIEditor::_save_bt);
ClassDB::bind_method(D_METHOD("_load_bt", "p_path"), &LimboAIEditor::_load_bt); ClassDB::bind_method(D_METHOD("_load_bt", "p_path"), &LimboAIEditor::_load_bt);
@ -751,6 +841,7 @@ LimboAIEditor::LimboAIEditor(EditorNode *p_editor) {
task_tree->connect("rmb_pressed", this, "_on_tree_rmb"); task_tree->connect("rmb_pressed", this, "_on_tree_rmb");
task_tree->connect("task_selected", this, "_on_tree_task_selected"); task_tree->connect("task_selected", this, "_on_tree_task_selected");
task_tree->connect("visibility_changed", this, "_on_visibility_changed"); task_tree->connect("visibility_changed", this, "_on_visibility_changed");
task_tree->connect("task_dragged", this, "_on_task_dragged");
TaskPanel *task_panel = memnew(TaskPanel(p_editor)); TaskPanel *task_panel = memnew(TaskPanel(p_editor));
hsc->add_child(task_panel); hsc->add_child(task_panel);
@ -778,7 +869,9 @@ LimboAIEditor::LimboAIEditor(EditorNode *p_editor) {
LimboAIEditor::~LimboAIEditor() { LimboAIEditor::~LimboAIEditor() {
} }
//////////////////////////////////////////////////////////////////////////////// //////////////////////////// LimboAIEditor ///////////////////////////////////
///////////////////////// LimboAIEditorPlugin ////////////////////////////////
const Ref<Texture> LimboAIEditorPlugin::get_icon() const { const Ref<Texture> LimboAIEditorPlugin::get_icon() const {
// TODO: // TODO:

View File

@ -8,6 +8,7 @@
#include "editor/editor_node.h" #include "editor/editor_node.h"
#include "editor/editor_plugin.h" #include "editor/editor_plugin.h"
#include "modules/limboai/bt/behavior_tree.h" #include "modules/limboai/bt/behavior_tree.h"
#include "modules/limboai/bt/bt_task.h"
#include "scene/gui/box_container.h" #include "scene/gui/box_container.h"
#include "scene/gui/file_dialog.h" #include "scene/gui/file_dialog.h"
#include "scene/gui/flow_container.h" #include "scene/gui/flow_container.h"
@ -22,6 +23,7 @@ private:
Tree *tree; Tree *tree;
Ref<BehaviorTree> bt; Ref<BehaviorTree> bt;
Ref<BTTask> last_selected; Ref<BTTask> last_selected;
bool editable;
TreeItem *_create_tree(const Ref<BTTask> &p_task, TreeItem *p_parent, int p_idx = -1); TreeItem *_create_tree(const Ref<BTTask> &p_task, TreeItem *p_parent, int p_idx = -1);
void _update_item(TreeItem *p_item); void _update_item(TreeItem *p_item);
@ -31,6 +33,10 @@ private:
void _on_item_selected(); void _on_item_selected();
void _on_item_rmb_selected(const Vector2 &p_pos); void _on_item_rmb_selected(const Vector2 &p_pos);
Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
protected: protected:
static void _bind_methods(); static void _bind_methods();
@ -133,6 +139,7 @@ private:
void _on_panel_task_selected(String p_task); void _on_panel_task_selected(String p_task);
void _on_history_back(); void _on_history_back();
void _on_history_forward(); void _on_history_forward();
void _on_task_dragged(Ref<BTTask> p_task, Ref<BTTask> p_to_task, int p_type);
protected: protected:
static void _bind_methods(); static void _bind_methods();