From 0bd60bae88b8f6b7e2a5d2f235d58034cfae5845 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Sat, 26 Aug 2023 10:24:08 +0200 Subject: [PATCH] Extract TaskTree code --- editor/limbo_ai_editor_plugin.cpp | 324 ------------------------------ editor/limbo_ai_editor_plugin.h | 44 +--- editor/task_tree.cpp | 293 +++++++++++++++++++++++++++ editor/task_tree.h | 58 ++++++ 4 files changed, 352 insertions(+), 367 deletions(-) create mode 100644 editor/task_tree.cpp create mode 100644 editor/task_tree.h diff --git a/editor/limbo_ai_editor_plugin.cpp b/editor/limbo_ai_editor_plugin.cpp index c77a0c5..66d3030 100644 --- a/editor/limbo_ai_editor_plugin.cpp +++ b/editor/limbo_ai_editor_plugin.cpp @@ -14,348 +14,24 @@ #include "limbo_ai_editor_plugin.h" #include "action_banner.h" -#include "modules/limboai/bt/behavior_tree.h" -#include "modules/limboai/bt/tasks/bt_action.h" #include "modules/limboai/bt/tasks/bt_comment.h" -#include "modules/limboai/bt/tasks/bt_task.h" -#include "modules/limboai/bt/tasks/composites/bt_parallel.h" #include "modules/limboai/bt/tasks/composites/bt_selector.h" -#include "modules/limboai/bt/tasks/composites/bt_sequence.h" #include "modules/limboai/editor/debugger/limbo_debugger_plugin.h" #include "modules/limboai/util/limbo_utility.h" #include "core/config/project_settings.h" -#include "core/error/error_list.h" -#include "core/error/error_macros.h" -#include "core/io/config_file.h" -#include "core/io/dir_access.h" -#include "core/io/image_loader.h" -#include "core/io/resource.h" -#include "core/io/resource_loader.h" -#include "core/io/resource_saver.h" -#include "core/math/math_defs.h" -#include "core/math/vector2.h" -#include "core/object/callable_method_pointer.h" -#include "core/object/class_db.h" -#include "core/object/object.h" -#include "core/object/script_language.h" -#include "core/object/undo_redo.h" -#include "core/os/memory.h" -#include "core/string/print_string.h" -#include "core/string/string_name.h" -#include "core/string/ustring.h" -#include "core/templates/list.h" -#include "core/templates/vector.h" -#include "core/typedefs.h" -#include "core/variant/array.h" -#include "core/variant/callable.h" -#include "core/variant/dictionary.h" -#include "core/variant/variant.h" #include "editor/debugger/editor_debugger_node.h" #include "editor/debugger/script_editor_debugger.h" #include "editor/editor_file_system.h" #include "editor/editor_help.h" -#include "editor/editor_inspector.h" -#include "editor/editor_node.h" #include "editor/editor_paths.h" -#include "editor/editor_plugin.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.h" #include "editor/inspector_dock.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/project_settings_editor.h" -#include "scene/gui/box_container.h" -#include "scene/gui/button.h" -#include "scene/gui/control.h" -#include "scene/gui/file_dialog.h" -#include "scene/gui/flow_container.h" -#include "scene/gui/label.h" -#include "scene/gui/line_edit.h" -#include "scene/gui/popup_menu.h" -#include "scene/gui/scroll_container.h" #include "scene/gui/separator.h" -#include "scene/gui/split_container.h" -#include "scene/gui/tree.h" -#include "servers/display_server.h" - -//**** TaskTree - -TreeItem *TaskTree::_create_tree(const Ref &p_task, TreeItem *p_parent, int p_idx) { - ERR_FAIL_COND_V(p_task.is_null(), nullptr); - TreeItem *item = tree->create_item(p_parent, p_idx); - item->set_metadata(0, p_task); - // p_task->connect("changed"...) - for (int i = 0; i < p_task->get_child_count(); i++) { - _create_tree(p_task->get_child(i), item); - } - _update_item(item); - return item; -} - -void TaskTree::_update_item(TreeItem *p_item) { - if (p_item == nullptr) { - return; - } - Ref task = p_item->get_metadata(0); - ERR_FAIL_COND_MSG(!task.is_valid(), "Invalid task reference in metadata."); - p_item->set_text(0, task->get_task_name()); - if (task->is_class_ptr(BTComment::get_class_ptr_static())) { - p_item->set_custom_font(0, (get_theme_font(SNAME("doc_italic"), SNAME("EditorFonts")))); - p_item->set_custom_color(0, get_theme_color(SNAME("disabled_font_color"), SNAME("Editor"))); - } else if (task->get_custom_name().is_empty()) { - p_item->set_custom_font(0, nullptr); - p_item->clear_custom_color(0); - } else { - p_item->set_custom_font(0, (get_theme_font(SNAME("bold"), SNAME("EditorFonts")))); - // p_item->set_custom_color(0, get_theme_color(SNAME("warning_color"), SNAME("Editor"))); - } - String type_arg; - if (task->get_script_instance() && !task->get_script_instance()->get_script()->get_path().is_empty()) { - type_arg = task->get_script_instance()->get_script()->get_path(); - } else { - type_arg = task->get_class(); - } - p_item->set_icon(0, LimboUtility::get_singleton()->get_task_icon(type_arg)); - p_item->set_icon_max_width(0, 16 * EDSCALE); - p_item->set_editable(0, false); - - for (int i = 0; i < p_item->get_button_count(0); i++) { - p_item->erase_button(0, i); - } - - PackedStringArray warnings = task->get_configuration_warnings(); - String warning_text; - for (int j = 0; j < warnings.size(); j++) { - if (j > 0) { - warning_text += "\n"; - } - warning_text += warnings[j]; - } - if (!warning_text.is_empty()) { - p_item->add_button(0, get_theme_icon(SNAME("NodeWarning"), SNAME("EditorIcons")), 0, false, warning_text); - } - - // TODO: Update probabilities. -} - -void TaskTree::_update_tree() { - Ref sel; - if (tree->get_selected()) { - sel = tree->get_selected()->get_metadata(0); - } - - tree->clear(); - if (bt.is_null()) { - return; - } - - if (bt->get_root_task().is_valid()) { - _create_tree(bt->get_root_task(), nullptr); - } - - TreeItem *item = _find_item(sel); - if (item) { - item->select(0); - } -} - -TreeItem *TaskTree::_find_item(const Ref &p_task) const { - if (p_task.is_null()) { - return nullptr; - } - TreeItem *item = tree->get_root(); - List stack; - while (item && item->get_metadata(0) != p_task) { - if (item->get_child_count() > 0) { - stack.push_back(item->get_first_child()); - } - item = item->get_next(); - if (item == nullptr && !stack.is_empty()) { - item = stack.front()->get(); - stack.pop_front(); - } - } - return item; -} - -void TaskTree::_on_item_mouse_selected(const Vector2 &p_pos, int p_button_index) { - if (p_button_index == 2) { - emit_signal(SNAME("rmb_pressed"), get_screen_position() + p_pos); - } -} - -void TaskTree::_on_item_selected() { - Callable on_task_changed = callable_mp(this, &TaskTree::_on_task_changed); - if (last_selected.is_valid()) { - update_task(last_selected); - if (last_selected->is_connected("changed", on_task_changed)) { - last_selected->disconnect("changed", on_task_changed); - } - } - last_selected = get_selected(); - last_selected->connect("changed", on_task_changed); - emit_signal(SNAME("task_selected"), last_selected); -} - -void TaskTree::_on_item_double_clicked() { - emit_signal(SNAME("task_double_clicked")); -} - -void TaskTree::_on_task_changed() { - _update_item(tree->get_selected()); -} - -void TaskTree::load_bt(const Ref &p_behavior_tree) { - ERR_FAIL_COND_MSG(p_behavior_tree.is_null(), "Tried to load a null tree."); - - Callable on_task_changed = callable_mp(this, &TaskTree::_on_task_changed); - if (last_selected.is_valid() && last_selected->is_connected("changed", on_task_changed)) { - last_selected->disconnect("changed", on_task_changed); - } - - bt = p_behavior_tree; - tree->clear(); - if (bt->get_root_task().is_valid()) { - _create_tree(bt->get_root_task(), nullptr); - } -} - -void TaskTree::unload() { - Callable on_task_changed = callable_mp(this, &TaskTree::_on_task_changed); - if (last_selected.is_valid() && last_selected->is_connected("changed", on_task_changed)) { - last_selected->disconnect("changed", on_task_changed); - } - - bt->unreference(); - tree->clear(); -} - -void TaskTree::update_task(const Ref &p_task) { - ERR_FAIL_COND(p_task.is_null()); - TreeItem *item = _find_item(p_task); - if (item) { - _update_item(item); - } -} - -Ref TaskTree::get_selected() const { - if (tree->get_selected()) { - return tree->get_selected()->get_metadata(0); - } - return nullptr; -} - -void TaskTree::deselect() { - TreeItem *sel = tree->get_selected(); - if (sel) { - sel->deselect(0); - } -} - -Variant TaskTree::_get_drag_data_fw(const Point2 &p_point) { - 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) 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 task = d["task"]; - const Ref 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) { - Dictionary d = p_data; - TreeItem *item = tree->get_item_at_position(p_point); - if (item && d.has("task")) { - Ref task = d["task"]; - emit_signal(SNAME("task_dragged"), task, item->get_metadata(0), tree->get_drop_section_at_position(p_point)); - } -} - -void TaskTree::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_THEME_CHANGED: { - _update_tree(); - } break; - } -} - -void TaskTree::_bind_methods() { - ClassDB::bind_method(D_METHOD("load_bt", "p_behavior_tree"), &TaskTree::load_bt); - ClassDB::bind_method(D_METHOD("get_bt"), &TaskTree::get_bt); - ClassDB::bind_method(D_METHOD("update_tree"), &TaskTree::update_tree); - ClassDB::bind_method(D_METHOD("update_task", "p_task"), &TaskTree::update_task); - ClassDB::bind_method(D_METHOD("get_selected"), &TaskTree::get_selected); - 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("task_selected")); - ADD_SIGNAL(MethodInfo("task_double_clicked")); - 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() { - editable = true; - - tree = memnew(Tree); - add_child(tree); - tree->set_columns(2); - tree->set_column_expand(0, true); - tree->set_column_expand(1, false); - tree->set_column_custom_minimum_width(1, 64); - tree->set_anchor(SIDE_RIGHT, ANCHOR_END); - tree->set_anchor(SIDE_BOTTOM, ANCHOR_END); - tree->set_allow_rmb_select(true); - tree->connect("item_mouse_selected", callable_mp(this, &TaskTree::_on_item_mouse_selected)); - tree->connect("item_selected", callable_mp(this, &TaskTree::_on_item_selected)); - tree->connect("item_activated", callable_mp(this, &TaskTree::_on_item_double_clicked)); - - tree->set_drag_forwarding(callable_mp(this, &TaskTree::_get_drag_data_fw), callable_mp(this, &TaskTree::_can_drop_data_fw), callable_mp(this, &TaskTree::_drop_data_fw)); -} - -TaskTree::~TaskTree() { - Callable on_task_changed = callable_mp(this, &TaskTree::_on_task_changed); - if (last_selected.is_valid() && last_selected->is_connected("changed", on_task_changed)) { - last_selected->disconnect("changed", on_task_changed); - } -} - -//**** TaskTree ^ //**** LimboAIEditor diff --git a/editor/limbo_ai_editor_plugin.h b/editor/limbo_ai_editor_plugin.h index 8933434..27fc67d 100644 --- a/editor/limbo_ai_editor_plugin.h +++ b/editor/limbo_ai_editor_plugin.h @@ -16,6 +16,7 @@ #include "modules/limboai/bt/behavior_tree.h" #include "modules/limboai/bt/tasks/bt_task.h" #include "task_palette.h" +#include "task_tree.h" #include "core/object/class_db.h" #include "core/object/object.h" @@ -34,49 +35,6 @@ #include "scene/gui/tree.h" #include "scene/resources/texture.h" -class TaskTree : public Control { - GDCLASS(TaskTree, Control); - -private: - Tree *tree; - Ref bt; - Ref last_selected; - bool editable; - - TreeItem *_create_tree(const Ref &p_task, TreeItem *p_parent, int p_idx = -1); - void _update_item(TreeItem *p_item); - void _update_tree(); - TreeItem *_find_item(const Ref &p_task) const; - - void _on_item_selected(); - void _on_item_double_clicked(); - void _on_item_mouse_selected(const Vector2 &p_pos, int p_button_index); - void _on_task_changed(); - - Variant _get_drag_data_fw(const Point2 &p_point); - bool _can_drop_data_fw(const Point2 &p_point, const Variant &p_data) const; - void _drop_data_fw(const Point2 &p_point, const Variant &p_data); - -protected: - static void _bind_methods(); - - void _notification(int p_what); - -public: - void load_bt(const Ref &p_behavior_tree); - void unload(); - Ref get_bt() const { return bt; } - void update_tree() { _update_tree(); } - void update_task(const Ref &p_task); - Ref get_selected() const; - void deselect(); - - virtual bool editor_can_reload_from_file() { return false; } - - TaskTree(); - ~TaskTree(); -}; - class LimboAIEditor : public Control { GDCLASS(LimboAIEditor, Control); diff --git a/editor/task_tree.cpp b/editor/task_tree.cpp new file mode 100644 index 0000000..88cf36a --- /dev/null +++ b/editor/task_tree.cpp @@ -0,0 +1,293 @@ +/** + * task_tree.cpp + * ============================================================================= + * Copyright 2021-2023 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#include "task_tree.h" + +#include "modules/limboai/bt/tasks/bt_comment.h" +#include "modules/limboai/util/limbo_utility.h" + +#include "editor/editor_scale.h" + +//**** TaskTree + +TreeItem *TaskTree::_create_tree(const Ref &p_task, TreeItem *p_parent, int p_idx) { + ERR_FAIL_COND_V(p_task.is_null(), nullptr); + TreeItem *item = tree->create_item(p_parent, p_idx); + item->set_metadata(0, p_task); + // p_task->connect("changed"...) + for (int i = 0; i < p_task->get_child_count(); i++) { + _create_tree(p_task->get_child(i), item); + } + _update_item(item); + return item; +} + +void TaskTree::_update_item(TreeItem *p_item) { + if (p_item == nullptr) { + return; + } + Ref task = p_item->get_metadata(0); + ERR_FAIL_COND_MSG(!task.is_valid(), "Invalid task reference in metadata."); + p_item->set_text(0, task->get_task_name()); + if (task->is_class_ptr(BTComment::get_class_ptr_static())) { + p_item->set_custom_font(0, (get_theme_font(SNAME("doc_italic"), SNAME("EditorFonts")))); + p_item->set_custom_color(0, get_theme_color(SNAME("disabled_font_color"), SNAME("Editor"))); + } else if (task->get_custom_name().is_empty()) { + p_item->set_custom_font(0, nullptr); + p_item->clear_custom_color(0); + } else { + p_item->set_custom_font(0, (get_theme_font(SNAME("bold"), SNAME("EditorFonts")))); + // p_item->set_custom_color(0, get_theme_color(SNAME("warning_color"), SNAME("Editor"))); + } + String type_arg; + if (task->get_script_instance() && !task->get_script_instance()->get_script()->get_path().is_empty()) { + type_arg = task->get_script_instance()->get_script()->get_path(); + } else { + type_arg = task->get_class(); + } + p_item->set_icon(0, LimboUtility::get_singleton()->get_task_icon(type_arg)); + p_item->set_icon_max_width(0, 16 * EDSCALE); + p_item->set_editable(0, false); + + for (int i = 0; i < p_item->get_button_count(0); i++) { + p_item->erase_button(0, i); + } + + PackedStringArray warnings = task->get_configuration_warnings(); + String warning_text; + for (int j = 0; j < warnings.size(); j++) { + if (j > 0) { + warning_text += "\n"; + } + warning_text += warnings[j]; + } + if (!warning_text.is_empty()) { + p_item->add_button(0, get_theme_icon(SNAME("NodeWarning"), SNAME("EditorIcons")), 0, false, warning_text); + } + + // TODO: Update probabilities. +} + +void TaskTree::_update_tree() { + Ref sel; + if (tree->get_selected()) { + sel = tree->get_selected()->get_metadata(0); + } + + tree->clear(); + if (bt.is_null()) { + return; + } + + if (bt->get_root_task().is_valid()) { + _create_tree(bt->get_root_task(), nullptr); + } + + TreeItem *item = _find_item(sel); + if (item) { + item->select(0); + } +} + +TreeItem *TaskTree::_find_item(const Ref &p_task) const { + if (p_task.is_null()) { + return nullptr; + } + TreeItem *item = tree->get_root(); + List stack; + while (item && item->get_metadata(0) != p_task) { + if (item->get_child_count() > 0) { + stack.push_back(item->get_first_child()); + } + item = item->get_next(); + if (item == nullptr && !stack.is_empty()) { + item = stack.front()->get(); + stack.pop_front(); + } + } + return item; +} + +void TaskTree::_on_item_mouse_selected(const Vector2 &p_pos, int p_button_index) { + if (p_button_index == 2) { + emit_signal(SNAME("rmb_pressed"), get_screen_position() + p_pos); + } +} + +void TaskTree::_on_item_selected() { + Callable on_task_changed = callable_mp(this, &TaskTree::_on_task_changed); + if (last_selected.is_valid()) { + update_task(last_selected); + if (last_selected->is_connected("changed", on_task_changed)) { + last_selected->disconnect("changed", on_task_changed); + } + } + last_selected = get_selected(); + last_selected->connect("changed", on_task_changed); + emit_signal(SNAME("task_selected"), last_selected); +} + +void TaskTree::_on_item_double_clicked() { + emit_signal(SNAME("task_double_clicked")); +} + +void TaskTree::_on_task_changed() { + _update_item(tree->get_selected()); +} + +void TaskTree::load_bt(const Ref &p_behavior_tree) { + ERR_FAIL_COND_MSG(p_behavior_tree.is_null(), "Tried to load a null tree."); + + Callable on_task_changed = callable_mp(this, &TaskTree::_on_task_changed); + if (last_selected.is_valid() && last_selected->is_connected("changed", on_task_changed)) { + last_selected->disconnect("changed", on_task_changed); + } + + bt = p_behavior_tree; + tree->clear(); + if (bt->get_root_task().is_valid()) { + _create_tree(bt->get_root_task(), nullptr); + } +} + +void TaskTree::unload() { + Callable on_task_changed = callable_mp(this, &TaskTree::_on_task_changed); + if (last_selected.is_valid() && last_selected->is_connected("changed", on_task_changed)) { + last_selected->disconnect("changed", on_task_changed); + } + + bt->unreference(); + tree->clear(); +} + +void TaskTree::update_task(const Ref &p_task) { + ERR_FAIL_COND(p_task.is_null()); + TreeItem *item = _find_item(p_task); + if (item) { + _update_item(item); + } +} + +Ref TaskTree::get_selected() const { + if (tree->get_selected()) { + return tree->get_selected()->get_metadata(0); + } + return nullptr; +} + +void TaskTree::deselect() { + TreeItem *sel = tree->get_selected(); + if (sel) { + sel->deselect(0); + } +} + +Variant TaskTree::_get_drag_data_fw(const Point2 &p_point) { + 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) 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 task = d["task"]; + const Ref 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) { + Dictionary d = p_data; + TreeItem *item = tree->get_item_at_position(p_point); + if (item && d.has("task")) { + Ref task = d["task"]; + emit_signal(SNAME("task_dragged"), task, item->get_metadata(0), tree->get_drop_section_at_position(p_point)); + } +} + +void TaskTree::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + _update_tree(); + } break; + } +} + +void TaskTree::_bind_methods() { + ClassDB::bind_method(D_METHOD("load_bt", "p_behavior_tree"), &TaskTree::load_bt); + ClassDB::bind_method(D_METHOD("get_bt"), &TaskTree::get_bt); + ClassDB::bind_method(D_METHOD("update_tree"), &TaskTree::update_tree); + ClassDB::bind_method(D_METHOD("update_task", "p_task"), &TaskTree::update_task); + ClassDB::bind_method(D_METHOD("get_selected"), &TaskTree::get_selected); + 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("task_selected")); + ADD_SIGNAL(MethodInfo("task_double_clicked")); + 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() { + editable = true; + + tree = memnew(Tree); + add_child(tree); + tree->set_columns(2); + tree->set_column_expand(0, true); + tree->set_column_expand(1, false); + tree->set_column_custom_minimum_width(1, 64); + tree->set_anchor(SIDE_RIGHT, ANCHOR_END); + tree->set_anchor(SIDE_BOTTOM, ANCHOR_END); + tree->set_allow_rmb_select(true); + tree->connect("item_mouse_selected", callable_mp(this, &TaskTree::_on_item_mouse_selected)); + tree->connect("item_selected", callable_mp(this, &TaskTree::_on_item_selected)); + tree->connect("item_activated", callable_mp(this, &TaskTree::_on_item_double_clicked)); + + tree->set_drag_forwarding(callable_mp(this, &TaskTree::_get_drag_data_fw), callable_mp(this, &TaskTree::_can_drop_data_fw), callable_mp(this, &TaskTree::_drop_data_fw)); +} + +TaskTree::~TaskTree() { + Callable on_task_changed = callable_mp(this, &TaskTree::_on_task_changed); + if (last_selected.is_valid() && last_selected->is_connected("changed", on_task_changed)) { + last_selected->disconnect("changed", on_task_changed); + } +} diff --git a/editor/task_tree.h b/editor/task_tree.h new file mode 100644 index 0000000..dd68895 --- /dev/null +++ b/editor/task_tree.h @@ -0,0 +1,58 @@ +/** + * task_tree.h + * ============================================================================= + * Copyright 2021-2023 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#include "modules/limboai/bt/behavior_tree.h" + +#include "scene/gui/control.h" +#include "scene/gui/tree.h" + +class TaskTree : public Control { + GDCLASS(TaskTree, Control); + +private: + Tree *tree; + Ref bt; + Ref last_selected; + bool editable; + + TreeItem *_create_tree(const Ref &p_task, TreeItem *p_parent, int p_idx = -1); + void _update_item(TreeItem *p_item); + void _update_tree(); + TreeItem *_find_item(const Ref &p_task) const; + + void _on_item_selected(); + void _on_item_double_clicked(); + void _on_item_mouse_selected(const Vector2 &p_pos, int p_button_index); + void _on_task_changed(); + + Variant _get_drag_data_fw(const Point2 &p_point); + bool _can_drop_data_fw(const Point2 &p_point, const Variant &p_data) const; + void _drop_data_fw(const Point2 &p_point, const Variant &p_data); + +protected: + static void _bind_methods(); + + void _notification(int p_what); + +public: + void load_bt(const Ref &p_behavior_tree); + void unload(); + Ref get_bt() const { return bt; } + void update_tree() { _update_tree(); } + void update_task(const Ref &p_task); + Ref get_selected() const; + void deselect(); + + virtual bool editor_can_reload_from_file() { return false; } + + TaskTree(); + ~TaskTree(); +}; \ No newline at end of file