From 15e0323919cbd7bf3f1fdf7eeef6f20420807ebf Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Wed, 24 Jan 2024 23:11:09 +0100 Subject: [PATCH] Implement BlackboardPlan editor --- blackboard/blackboard_plan.cpp | 77 +++++- blackboard/blackboard_plan.h | 13 +- bt/bt_player.cpp | 16 +- editor/blackboard_plan_editor.cpp | 411 ++++++++++++++++++++++++++++++ editor/blackboard_plan_editor.h | 95 +++++++ editor/limbo_ai_editor_plugin.cpp | 6 +- util/limbo_string_names.cpp | 11 + util/limbo_string_names.h | 11 + util/limbo_utility.cpp | 123 +++++++++ util/limbo_utility.h | 2 + 10 files changed, 745 insertions(+), 20 deletions(-) create mode 100644 editor/blackboard_plan_editor.cpp create mode 100644 editor/blackboard_plan_editor.h diff --git a/blackboard/blackboard_plan.cpp b/blackboard/blackboard_plan.cpp index b7b2508..a02869c 100644 --- a/blackboard/blackboard_plan.cpp +++ b/blackboard/blackboard_plan.cpp @@ -108,11 +108,13 @@ void BlackboardPlan::set_base_plan(const Ref &p_base) { base = p_base; sync_with_base_plan(); emit_changed(); + notify_property_list_changed(); } void BlackboardPlan::set_value(const String &p_name, const Variant &p_value) { ERR_FAIL_COND(!data.has(p_name)); data.get(p_name).set_value(p_value); + emit_changed(); } Variant BlackboardPlan::get_value(const String &p_name) const { @@ -124,12 +126,16 @@ void BlackboardPlan::add_var(const String &p_name, const BBVariable &p_var) { ERR_FAIL_COND(data.has(p_name)); ERR_FAIL_COND(base.is_valid()); data.insert(p_name, p_var); + notify_property_list_changed(); + emit_changed(); } void BlackboardPlan::remove_var(const String &p_name) { ERR_FAIL_COND(!data.has(p_name)); ERR_FAIL_COND(base.is_valid()); data.erase(p_name); + notify_property_list_changed(); + emit_changed(); } BBVariable BlackboardPlan::get_var(const String &p_name) { @@ -137,6 +143,22 @@ BBVariable BlackboardPlan::get_var(const String &p_name) { return data.get(p_name); } +Pair BlackboardPlan::get_var_by_index(int p_index) { + Pair ret; + ERR_FAIL_INDEX_V(p_index, (int)data.size(), ret); + + int i = 0; + for (const KeyValue &kv : data) { + if (i == p_index) { + ret.first = kv.key; + ret.second = kv.value; + } + i += 1; + } + + return ret; +} + PackedStringArray BlackboardPlan::list_vars() const { PackedStringArray ret; for (const KeyValue &kv : data) { @@ -145,24 +167,66 @@ PackedStringArray BlackboardPlan::list_vars() const { return ret; } +String BlackboardPlan::get_var_name(const BBVariable &p_var) const { + for (const KeyValue &kv : data) { + if (kv.value == p_var) { + return kv.key; + } + } + return String(); +} + +void BlackboardPlan::rename_var(const String &p_name, const String &p_new_name) { + ERR_FAIL_COND(p_new_name.is_empty()); + ERR_FAIL_COND(data.has(p_new_name)); + ERR_FAIL_COND(!data.has(p_name)); + + data.replace_key(p_name, p_new_name); + notify_property_list_changed(); + emit_changed(); +} + +void BlackboardPlan::swap_vars(int p_idx_a, int p_idx_b) { + ERR_FAIL_INDEX(p_idx_a, (int)data.size()); + ERR_FAIL_INDEX(p_idx_b, (int)data.size()); + + Pair a = get_var_by_index(p_idx_a); + Pair b = get_var_by_index(p_idx_b); + + data.replace_key(a.first, "__tmp__"); + data.replace_key(b.first, a.first); + data.replace_key("__tmp__", b.first); + + data[b.first] = b.second; + data[a.first] = a.second; + + notify_property_list_changed(); + emit_changed(); +} + void BlackboardPlan::sync_with_base_plan() { if (base.is_null()) { return; } + bool changed = false; + // Sync variables with the base plan. for (const KeyValue &kv : base->data) { if (!data.has(kv.key)) { data.insert(kv.key, kv.value.duplicate()); + changed = true; continue; } BBVariable var = data.get(kv.key); if (!var.is_same_prop_info(kv.value)) { var.copy_prop_info(kv.value); + changed = true; } if (var.get_value().get_type() != kv.value.get_type()) { var.set_value(kv.value.get_value()); + changed = true; } } @@ -170,8 +234,14 @@ void BlackboardPlan::sync_with_base_plan() { for (const KeyValue &kv : data) { if (!base->data.has(kv.key)) { data.erase(kv.key); + changed = true; } } + + if (changed) { + notify_property_list_changed(); + emit_changed(); + } } Ref BlackboardPlan::create_blackboard() { @@ -196,11 +266,4 @@ void BlackboardPlan::populate_blackboard(const Ref &p_blackboard, bo } BlackboardPlan::BlackboardPlan() { - // TODO: REMOVE THE TEST DATA BELOW. - data.insert("speed", BBVariable(Variant::Type::FLOAT)); - data["speed"].set_value(200.0); - data.insert("limit_speed", BBVariable(Variant::Type::BOOL)); - data["limit_speed"].set_value(500.0); - data.insert("about", BBVariable(Variant::Type::STRING, PropertyHint::PROPERTY_HINT_MULTILINE_TEXT, "")); - data["about"].set_value("Hello, World!"); } diff --git a/blackboard/blackboard_plan.h b/blackboard/blackboard_plan.h index 6c2a6b7..ed6c55e 100644 --- a/blackboard/blackboard_plan.h +++ b/blackboard/blackboard_plan.h @@ -24,7 +24,8 @@ private: HashMap data; // When base is not null, the plan is considered to be derived from the base plan. - // A derived plan can only have variables that exist in the base plan. + // A derived plan can only have variables that exist in the base plan, + // and only the values can be different in those variables. Ref base; protected: @@ -40,11 +41,19 @@ public: void set_value(const String &p_name, const Variant &p_value); Variant get_value(const String &p_name) const; + void add_var(const String &p_name, const BBVariable &p_var); void remove_var(const String &p_name); BBVariable get_var(const String &p_name); - PackedStringArray list_vars() const; + Pair get_var_by_index(int p_index); + bool has_var(const String &p_name) { return data.has(p_name); } bool is_empty() const { return data.is_empty(); } + int get_var_count() const { return data.size(); } + + PackedStringArray list_vars() const; + String get_var_name(const BBVariable &p_var) const; + void rename_var(const String &p_name, const String &p_new_name); + void swap_vars(int idx_a, int idx_b); void sync_with_base_plan(); bool is_derived() { return base.is_valid(); } diff --git a/bt/bt_player.cpp b/bt/bt_player.cpp index b5fc825..9c8724b 100644 --- a/bt/bt_player.cpp +++ b/bt/bt_player.cpp @@ -67,20 +67,18 @@ void BTPlayer::_update_blackboard_plan() { if (blackboard_plan.is_null()) { blackboard_plan = Ref(memnew(BlackboardPlan)); } - - if (behavior_tree.is_valid()) { - if (blackboard_plan == behavior_tree->get_blackboard_plan()) { - blackboard_plan->sync_with_base_plan(); - } else { - blackboard_plan->set_base_plan(behavior_tree->get_blackboard_plan()); - } - } + blackboard_plan->set_base_plan(behavior_tree.is_valid() ? behavior_tree->get_blackboard_plan() : nullptr); } void BTPlayer::set_behavior_tree(const Ref &p_tree) { + if (behavior_tree.is_valid() && behavior_tree->is_connected(LW_NAME(changed), callable_mp(this, &BTPlayer::_update_blackboard_plan))) { + behavior_tree->disconnect(LW_NAME(changed), callable_mp(this, &BTPlayer::_update_blackboard_plan)); + } behavior_tree = p_tree; - if (Engine::get_singleton()->is_editor_hint() == false && get_owner()) { + if (!Engine::get_singleton()->is_editor_hint() && get_owner()) { _load_tree(); + } else if (behavior_tree.is_valid()) { + behavior_tree->connect(LW_NAME(changed), callable_mp(this, &BTPlayer::_update_blackboard_plan)); } _update_blackboard_plan(); } diff --git a/editor/blackboard_plan_editor.cpp b/editor/blackboard_plan_editor.cpp new file mode 100644 index 0000000..94a96ca --- /dev/null +++ b/editor/blackboard_plan_editor.cpp @@ -0,0 +1,411 @@ +/** + * blackboard_plan_editor.cpp + * ============================================================================= + * Copyright 2021-2024 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 "blackboard_plan_editor.h" + +#include "../util/limbo_string_names.h" +#include "../util/limbo_utility.h" + +#ifdef LIMBOAI_MODULE +#include "editor/editor_interface.h" +#include "editor/editor_scale.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/panel_container.h" +#include "scene/resources/style_box_flat.h" +#endif // LIMBOAI_MODULE + +void BlackboardPlanEditor::_add_var() { + ERR_FAIL_NULL(plan); + + int suffix = 1; + String name = "var" + itos(suffix); + while (plan->has_var(name)) { + suffix += 1; + name = "var" + itos(suffix); + } + + BBVariable var(Variant::Type::FLOAT); + plan->add_var(name, var); + _refresh(); +} + +void BlackboardPlanEditor::_trash_var(int p_index) { + ERR_FAIL_NULL(plan); + String var_name = plan->get_var_by_index(p_index).first; + plan->remove_var(var_name); + _refresh(); +} + +void BlackboardPlanEditor::_rename_var(const String &p_new_name, int p_index) { + ERR_FAIL_NULL(plan); + plan->rename_var(plan->get_var_by_index(p_index).first, p_new_name); +} + +void BlackboardPlanEditor::_change_var_type(Variant::Type p_new_type, int p_index) { + ERR_FAIL_NULL(plan); + plan->get_var_by_index(p_index).second.set_type(p_new_type); + plan->notify_property_list_changed(); + _refresh(); +} + +void BlackboardPlanEditor::_change_var_hint(PropertyHint p_new_hint, int p_index) { + ERR_FAIL_NULL(plan); + plan->get_var_by_index(p_index).second.set_hint(p_new_hint); + plan->notify_property_list_changed(); + _refresh(); +} + +void BlackboardPlanEditor::_change_var_hint_string(const String &p_new_hint_string, int p_index) { + ERR_FAIL_NULL(plan); + plan->get_var_by_index(p_index).second.set_hint_string(p_new_hint_string); + plan->notify_property_list_changed(); +} + +void BlackboardPlanEditor::edit_plan(const Ref &p_plan) { + plan = p_plan; + _refresh(); +} + +void BlackboardPlanEditor::_show_button_popup(Button *p_button, PopupMenu *p_popup, int p_index) { + ERR_FAIL_NULL(p_button); + ERR_FAIL_NULL(p_popup); + + Rect2 rect = p_button->get_screen_rect(); + rect.position.y += rect.size.height; + rect.size.height = 0; + p_popup->set_size(rect.size); + p_popup->set_position(rect.position); + + last_index = p_index; + p_popup->popup(); +} + +void BlackboardPlanEditor::_type_chosen(int id) { + _change_var_type(Variant::Type(id), last_index); +} + +void BlackboardPlanEditor::_hint_chosen(int id) { + _change_var_hint(PropertyHint(id), last_index); +} + +void BlackboardPlanEditor::_drag_button_down(Control *p_row) { + drag_index = p_row->get_index(); + drag_mouse_y_delta = 0.0; + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED); +} + +void BlackboardPlanEditor::_drag_button_up() { + drag_index = -1; + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); + _refresh(); +} + +void BlackboardPlanEditor::_drag_button_gui_input(const Ref &p_event) { + if (drag_index < 0) { + return; + } + + Ref mm = p_event; + if (mm.is_null()) { + return; + } + + drag_mouse_y_delta += mm->get_relative().y; + + if ((drag_index == 0 && drag_mouse_y_delta < 0.0) || (drag_index == (plan->get_var_count() - 1) && drag_mouse_y_delta > 0.0)) { + drag_mouse_y_delta = 0.0; + return; + } + + float required_distance = 20.0f * EDSCALE; + if (ABS(drag_mouse_y_delta) > required_distance) { + int drag_dir = drag_mouse_y_delta > 0.0f ? 1 : -1; + drag_mouse_y_delta -= required_distance * drag_dir; + + plan->swap_vars(drag_index, drag_index + drag_dir); + + Control *row = Object::cast_to(rows_vbox->get_child(drag_index)); + Control *other_row = Object::cast_to(rows_vbox->get_child(drag_index + drag_dir)); + ERR_FAIL_NULL(row); + ERR_FAIL_NULL(other_row); + rows_vbox->move_child(row, drag_index + drag_dir); + row->add_theme_style_override(LW_NAME(panel), row->get_index() % 2 ? theme_cache.odd_style : theme_cache.even_style); + other_row->add_theme_style_override(LW_NAME(panel), other_row->get_index() % 2 ? theme_cache.odd_style : theme_cache.even_style); + + drag_index += drag_dir; + } +} + +void BlackboardPlanEditor::_visibility_changed() { + if (!is_visible() && plan.is_valid()) { + plan->notify_property_list_changed(); + } +} + +void BlackboardPlanEditor::_refresh() { + for (int i = 0; i < rows_vbox->get_child_count(); i++) { + Control *child = Object::cast_to(rows_vbox->get_child(i)); + ERR_FAIL_NULL(child); + child->hide(); + child->queue_free(); + } + + // TODO: Name validation + + PackedStringArray names = plan->list_vars(); + int idx = 0; + for (const String &var_name : names) { + BBVariable var = plan->get_var(var_name); + + PanelContainer *row_panel = memnew(PanelContainer); + rows_vbox->add_child(row_panel); + row_panel->add_theme_style_override(LW_NAME(panel), idx % 2 ? theme_cache.odd_style : theme_cache.even_style); + row_panel->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + HBoxContainer *props_hbox = memnew(HBoxContainer); + row_panel->add_child(props_hbox); + props_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + Button *drag_button = memnew(Button); + props_hbox->add_child(drag_button); + drag_button->set_custom_minimum_size(Size2(28.0, 28.0) * EDSCALE); + drag_button->set_icon(theme_cache.grab_icon); + drag_button->connect(LW_NAME(gui_input), callable_mp(this, &BlackboardPlanEditor::_drag_button_gui_input)); + drag_button->connect(LW_NAME(button_down), callable_mp(this, &BlackboardPlanEditor::_drag_button_down).bind(row_panel)); + drag_button->connect(LW_NAME(button_up), callable_mp(this, &BlackboardPlanEditor::_drag_button_up)); + + LineEdit *name_edit = memnew(LineEdit); + props_hbox->add_child(name_edit); + name_edit->set_text(var_name); + name_edit->set_placeholder(TTR("Variable name")); + name_edit->set_flat(true); + name_edit->set_custom_minimum_size(Size2(300.0, 0.0) * EDSCALE); + name_edit->connect(LW_NAME(text_changed), callable_mp(this, &BlackboardPlanEditor::_rename_var).bind(idx)); + name_edit->connect(LW_NAME(text_submitted), callable_mp(this, &BlackboardPlanEditor::_refresh).unbind(1)); + + Button *type_choice = memnew(Button); + props_hbox->add_child(type_choice); + type_choice->set_custom_minimum_size(Size2(170, 0.0) * EDSCALE); + type_choice->set_text(Variant::get_type_name(var.get_type())); + type_choice->set_tooltip_text(Variant::get_type_name(var.get_type())); + type_choice->set_icon(get_theme_icon(Variant::get_type_name(var.get_type()), LW_NAME(EditorIcons))); + type_choice->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); + type_choice->set_flat(true); + type_choice->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); + type_choice->connect(LW_NAME(pressed), callable_mp(this, &BlackboardPlanEditor::_show_button_popup).bind(type_choice, type_menu, idx)); + + Button *hint_choice = memnew(Button); + props_hbox->add_child(hint_choice); + hint_choice->set_custom_minimum_size(Size2(150.0, 0.0) * EDSCALE); + hint_choice->set_text(LimboUtility::get_singleton()->get_property_hint_text(var.get_hint())); + hint_choice->set_tooltip_text(LimboUtility::get_singleton()->get_property_hint_text(var.get_hint())); + hint_choice->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); + hint_choice->set_flat(true); + hint_choice->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); + hint_choice->connect(LW_NAME(pressed), callable_mp(this, &BlackboardPlanEditor::_show_button_popup).bind(hint_choice, hint_menu, idx)); + + LineEdit *hint_string_edit = memnew(LineEdit); + props_hbox->add_child(hint_string_edit); + hint_string_edit->set_custom_minimum_size(Size2(300.0, 0.0) * EDSCALE); + hint_string_edit->set_text(var.get_hint_string()); + hint_string_edit->set_placeholder(TTR("Hint string")); + hint_string_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); + hint_string_edit->set_flat(true); + hint_string_edit->connect(LW_NAME(text_changed), callable_mp(this, &BlackboardPlanEditor::_change_var_hint_string).bind(idx)); + hint_string_edit->connect(LW_NAME(text_submitted), callable_mp(this, &BlackboardPlanEditor::_refresh).unbind(1)); + + Button *trash_button = memnew(Button); + props_hbox->add_child(trash_button); + trash_button->set_custom_minimum_size(Size2(24.0, 0.0) * EDSCALE); + trash_button->set_icon(theme_cache.trash_icon); + trash_button->connect(LW_NAME(pressed), callable_mp(this, &BlackboardPlanEditor::_trash_var).bind(idx)); + + idx += 1; + } +} + +void BlackboardPlanEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + theme_cache.trash_icon = get_theme_icon(LW_NAME(Remove), LW_NAME(EditorIcons)); + theme_cache.grab_icon = get_theme_icon(LW_NAME(TripleBar), LW_NAME(EditorIcons)); + + add_var_tool->set_icon(get_theme_icon(LW_NAME(Add), LW_NAME(EditorIcons))); + + type_menu->clear(); + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + if (i == Variant::RID || i == Variant::CALLABLE || i == Variant::SIGNAL) { + continue; + } + String type = Variant::get_type_name(Variant::Type(i)); + type_menu->add_icon_item(get_theme_icon(type, LW_NAME(EditorIcons)), type, i); + } + + scroll_container->add_theme_style_override(LW_NAME(panel), get_theme_stylebox(LW_NAME(panel), LW_NAME(Tree))); + + Color bg_color = get_theme_color(LW_NAME(dark_color_2), LW_NAME(Editor)); + theme_cache.odd_style->set_bg_color(bg_color.darkened(-0.05)); + theme_cache.even_style->set_bg_color(bg_color.darkened(0.05)); + theme_cache.header_style->set_bg_color(bg_color.darkened(-0.2)); + + header_row->add_theme_style_override(LW_NAME(panel), theme_cache.header_style); + } break; + case NOTIFICATION_READY: { + add_var_tool->connect(LW_NAME(pressed), callable_mp(this, &BlackboardPlanEditor::_add_var)); + connect(LW_NAME(visibility_changed), callable_mp(this, &BlackboardPlanEditor::_visibility_changed)); + } break; + } +} + +BlackboardPlanEditor::BlackboardPlanEditor() { + set_title(TTR("Edit Blackboard Plan")); + + VBoxContainer *vbox = memnew(VBoxContainer); + vbox->add_theme_constant_override(LW_NAME(separation), 8 * EDSCALE); + add_child(vbox); + + HBoxContainer *toolbar = memnew(HBoxContainer); + vbox->add_child(toolbar); + + add_var_tool = memnew(Button); + toolbar->add_child(add_var_tool); + add_var_tool->set_focus_mode(Control::FOCUS_NONE); + add_var_tool->set_text(TTR("Add variable")); + + { + // * Header + header_row = memnew(PanelContainer); + vbox->add_child(header_row); + header_row->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + HBoxContainer *labels_hbox = memnew(HBoxContainer); + header_row->add_child(labels_hbox); + labels_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + Control *offset = memnew(Control); + labels_hbox->add_child(offset); + offset->set_custom_minimum_size(Size2(2.0, 0.0) * EDSCALE); + + Label *drag_header = memnew(Label); + labels_hbox->add_child(drag_header); + drag_header->set_custom_minimum_size(Size2(28.0, 28.0) * EDSCALE); + + Label *name_header = memnew(Label); + labels_hbox->add_child(name_header); + name_header->set_text(TTR("Name")); + name_header->set_custom_minimum_size(Size2(300.0, 0.0) * EDSCALE); + name_header->set_theme_type_variation(LW_NAME(HeaderSmall)); + + Label *type_header = memnew(Label); + labels_hbox->add_child(type_header); + type_header->set_text(TTR("Type")); + type_header->set_custom_minimum_size(Size2(170, 0.0) * EDSCALE); + type_header->set_theme_type_variation(LW_NAME(HeaderSmall)); + + Label *hint_header = memnew(Label); + labels_hbox->add_child(hint_header); + hint_header->set_text(TTR("Hint")); + hint_header->set_custom_minimum_size(Size2(150.0, 0.0) * EDSCALE); + hint_header->set_theme_type_variation(LW_NAME(HeaderSmall)); + + Label *hint_string_header = memnew(Label); + labels_hbox->add_child(hint_string_header); + hint_string_header->set_text(TTR("Hint string")); + hint_string_header->set_custom_minimum_size(Size2(300.0, 0.0) * EDSCALE); + hint_string_header->set_h_size_flags(Control::SIZE_EXPAND_FILL); + hint_string_header->set_theme_type_variation(LW_NAME(HeaderSmall)); + } + + scroll_container = memnew(ScrollContainer); + vbox->add_child(scroll_container); + scroll_container->set_v_size_flags(Control::SIZE_EXPAND_FILL); + scroll_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); + scroll_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); + scroll_container->set_custom_minimum_size(Size2(0.0, 600.0) * EDSCALE); + + rows_vbox = memnew(VBoxContainer); + scroll_container->add_child(rows_vbox); + rows_vbox->set_h_size_flags(Control::SIZE_EXPAND_FILL); + rows_vbox->add_theme_constant_override(LW_NAME(separation), 0); + + type_menu = memnew(PopupMenu); + add_child(type_menu); + type_menu->connect(LW_NAME(id_pressed), callable_mp(this, &BlackboardPlanEditor::_type_chosen)); + + hint_menu = memnew(PopupMenu); + add_child(hint_menu); + hint_menu->connect(LW_NAME(id_pressed), callable_mp(this, &BlackboardPlanEditor::_hint_chosen)); + for (int i = 0; i < PropertyHint::PROPERTY_HINT_MAX; i++) { + hint_menu->add_item(LimboUtility::get_singleton()->get_property_hint_text(PropertyHint(i)), i); + } + + theme_cache.odd_style.instantiate(); + theme_cache.even_style.instantiate(); + theme_cache.header_style.instantiate(); +} + +// ***** + +void EditorInspectorPluginBBPlan::_edit_plan(const Ref &p_plan) { + ERR_FAIL_NULL(p_plan); + plan_editor->edit_plan(p_plan); + plan_editor->popup_centered(); +} + +void EditorInspectorPluginBBPlan::_open_base_plan(const Ref &p_plan) { + ERR_FAIL_NULL(p_plan); + ERR_FAIL_NULL(p_plan->get_base_plan()); + EditorInterface::get_singleton()->call_deferred("edit_resource", p_plan->get_base_plan()); +} + +bool EditorInspectorPluginBBPlan::can_handle(Object *p_object) { + Ref plan = Object::cast_to(p_object); + if (plan.is_valid()) { + plan->sync_with_base_plan(); + } + return plan.is_valid(); +} + +void EditorInspectorPluginBBPlan::parse_begin(Object *p_object) { + Ref plan = Object::cast_to(p_object); + ERR_FAIL_NULL(plan); + + MarginContainer *margin_container = memnew(MarginContainer); + margin_container->set_theme_type_variation("MarginContainer4px"); + + VBoxContainer *toolbar = memnew(VBoxContainer); + margin_container->add_child(toolbar); + toolbar->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + + if (plan->is_derived()) { + Button *goto_btn = memnew(Button); + toolbar->add_child(goto_btn); + goto_btn->set_text(TTR("Open Base Plan")); + goto_btn->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + goto_btn->set_custom_minimum_size(Size2(200.0, 0.0) * EDSCALE); + goto_btn->connect(LW_NAME(pressed), callable_mp(this, &EditorInspectorPluginBBPlan::_open_base_plan).bind(plan)); + } else { + Button *edit_btn = memnew(Button); + toolbar->add_child(edit_btn); + edit_btn->set_text(TTR("Edit...")); + edit_btn->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + edit_btn->set_custom_minimum_size(Size2(200.0, 0.0) * EDSCALE); + edit_btn->connect(LW_NAME(pressed), callable_mp(this, &EditorInspectorPluginBBPlan::_edit_plan).bind(plan)); + } + + add_custom_control(margin_container); +} + +EditorInspectorPluginBBPlan::EditorInspectorPluginBBPlan() { + plan_editor = memnew(BlackboardPlanEditor); + EditorInterface::get_singleton()->get_base_control()->add_child(plan_editor); + plan_editor->hide(); +} diff --git a/editor/blackboard_plan_editor.h b/editor/blackboard_plan_editor.h new file mode 100644 index 0000000..2c6eaaa --- /dev/null +++ b/editor/blackboard_plan_editor.h @@ -0,0 +1,95 @@ +/** + * blackboard_plan_editor.h + * ============================================================================= + * Copyright 2021-2024 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. + * ============================================================================= + */ + +#ifndef BLACKBOARD_PLAN_EDITOR_H +#define BLACKBOARD_PLAN_EDITOR_H + +#include "../blackboard/blackboard_plan.h" + +#ifdef LIMBOAI_MODULE +#include "editor/editor_inspector.h" +#include "scene/gui/dialogs.h" +#endif // LIMBOAI_MODULE + +// ***** + +class BlackboardPlanEditor : public AcceptDialog { + GDCLASS(BlackboardPlanEditor, AcceptDialog); + +private: + struct ThemeCache { + Ref trash_icon; + Ref grab_icon; + Ref odd_style; + Ref even_style; + Ref header_style; + } theme_cache; + + int last_index = 0; + + int drag_mouse_y_delta = 0; + int drag_index = 0; + + Ref plan; + + VBoxContainer *rows_vbox; + Button *add_var_tool; + PanelContainer *header_row; + ScrollContainer *scroll_container; + PopupMenu *type_menu; + PopupMenu *hint_menu; + + void _add_var(); + void _trash_var(int p_index); + void _rename_var(const String &p_new_name, int p_index); + void _change_var_type(Variant::Type p_new_type, int p_index); + void _change_var_hint(PropertyHint p_new_hint, int p_index); + void _change_var_hint_string(const String &p_new_hint_string, int p_index); + + void _show_button_popup(Button *p_button, PopupMenu *p_popup, int p_index); + void _type_chosen(int id); + void _hint_chosen(int id); + + void _drag_button_down(Control *p_row); + void _drag_button_up(); + void _drag_button_gui_input(const Ref &p_event); + + void _refresh(); + void _visibility_changed(); + +protected: + void _notification(int p_what); + +public: + void edit_plan(const Ref &p_plan); + + BlackboardPlanEditor(); +}; + +// ***** + +class EditorInspectorPluginBBPlan : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginBBPlan, EditorInspectorPlugin); + +private: + BlackboardPlanEditor *plan_editor; + + void _edit_plan(const Ref &p_plan); + void _open_base_plan(const Ref &p_plan); + +public: + virtual bool can_handle(Object *p_object) override; + virtual void parse_begin(Object *p_object) override; + + EditorInspectorPluginBBPlan(); +}; + +#endif // BLACKBOARD_PLAN_EDITOR_H diff --git a/editor/limbo_ai_editor_plugin.cpp b/editor/limbo_ai_editor_plugin.cpp index 08e30ac..6477c29 100644 --- a/editor/limbo_ai_editor_plugin.cpp +++ b/editor/limbo_ai_editor_plugin.cpp @@ -18,11 +18,12 @@ #include "../bt/tasks/composites/bt_probability_selector.h" #include "../bt/tasks/composites/bt_selector.h" #include "../bt/tasks/decorators/bt_subtree.h" -#include "../editor/debugger/limbo_debugger_plugin.h" -#include "../editor/editor_property_bb_param.h" #include "../util/limbo_compat.h" #include "../util/limbo_utility.h" #include "action_banner.h" +#include "blackboard_plan_editor.h" +#include "debugger/limbo_debugger_plugin.h" +#include "editor_property_bb_param.h" #ifdef LIMBOAI_MODULE #include "core/config/project_settings.h" @@ -1447,6 +1448,7 @@ LimboAIEditorPlugin::LimboAIEditorPlugin() { limbo_ai_editor->hide(); limbo_ai_editor->set_plugin(this); + add_inspector_plugin(memnew(EditorInspectorPluginBBPlan)); #ifdef LIMBOAI_MODULE // ! Only used in the module version. add_inspector_plugin(memnew(EditorInspectorPluginBBParam)); diff --git a/util/limbo_string_names.cpp b/util/limbo_string_names.cpp index ee045bd..ab92007 100644 --- a/util/limbo_string_names.cpp +++ b/util/limbo_string_names.cpp @@ -42,6 +42,7 @@ LimboStringNames::LimboStringNames() { _update_banners = SN("_update_banners"); _weight_ = SN("_weight_"); accent_color = SN("accent_color"); + Add = SN("Add"); add_child = SN("add_child"); add_child_at_index = SN("add_child_at_index"); AnimationFilter = SN("AnimationFilter"); @@ -52,8 +53,12 @@ LimboStringNames::LimboStringNames() { bold = SN("bold"); BTAlwaysFail = SN("BTAlwaysFail"); BTAlwaysSucceed = SN("BTAlwaysSucceed"); + button_down = SN("button_down"); + button_up = SN("button_up"); changed = SN("changed"); connect = SN("connect"); + dark_color_1 = SN("dark_color_1"); + dark_color_2 = SN("dark_color_2"); Debug = SN("Debug"); disabled_font_color = SN("disabled_font_color"); doc_italic = SN("doc_italic"); @@ -76,6 +81,7 @@ LimboStringNames::LimboStringNames() { gui_input = SN("gui_input"); GuiTreeArrowDown = SN("GuiTreeArrowDown"); GuiTreeArrowRight = SN("GuiTreeArrowRight"); + HeaderSmall = SN("HeaderSmall"); Help = SN("Help"); icon_max_width = SN("icon_max_width"); id_pressed = SN("id_pressed"); @@ -97,6 +103,7 @@ LimboStringNames::LimboStringNames() { NodeWarning = SN("NodeWarning"); NonFavorite = SN("NonFavorite"); normal = SN("normal"); + panel = SN("panel"); popup_hide = SN("popup_hide"); pressed = SN("pressed"); probability_clicked = SN("probability_clicked"); @@ -111,6 +118,7 @@ LimboStringNames::LimboStringNames() { Script = SN("Script"); ScriptCreate = SN("ScriptCreate"); Search = SN("Search"); + separation = SN("separation"); set_custom_name = SN("set_custom_name"); set_root_task = SN("set_root_task"); setup = SN("setup"); @@ -125,9 +133,12 @@ LimboStringNames::LimboStringNames() { task_meta = SN("task_meta"); task_selected = SN("task_selected"); text_changed = SN("text_changed"); + text_submitted = SN("text_submitted"); timeout = SN("timeout"); toggled = SN("toggled"); Tools = SN("Tools"); + Tree = SN("Tree"); + TripleBar = SN("TripleBar"); update_task = SN("update_task"); update_tree = SN("update_tree"); updated = SN("updated"); diff --git a/util/limbo_string_names.h b/util/limbo_string_names.h index d1bd93f..85d7cf1 100644 --- a/util/limbo_string_names.h +++ b/util/limbo_string_names.h @@ -58,6 +58,7 @@ public: StringName accent_color; StringName add_child_at_index; StringName add_child; + StringName Add; StringName AnimationFilter; StringName Back; StringName behavior_tree_finished; @@ -66,8 +67,12 @@ public: StringName bold; StringName BTAlwaysFail; StringName BTAlwaysSucceed; + StringName button_down; + StringName button_up; StringName changed; StringName connect; + StringName dark_color_1; + StringName dark_color_2; StringName Debug; StringName disabled_font_color; StringName doc_italic; @@ -90,6 +95,7 @@ public: StringName gui_input; StringName GuiTreeArrowDown; StringName GuiTreeArrowRight; + StringName HeaderSmall; StringName Help; StringName icon_max_width; StringName id_pressed; @@ -112,6 +118,7 @@ public: StringName NodeWarning; StringName NonFavorite; StringName normal; + StringName panel; StringName popup_hide; StringName pressed; StringName probability_clicked; @@ -126,6 +133,7 @@ public: StringName Script; StringName ScriptCreate; StringName Search; + StringName separation; StringName set_custom_name; StringName set_root_task; StringName setup; @@ -140,9 +148,12 @@ public: StringName task_meta; StringName task_selected; StringName text_changed; + StringName text_submitted; StringName timeout; StringName toggled; StringName Tools; + StringName Tree; + StringName TripleBar; StringName update_task; StringName update_tree; StringName updated; diff --git a/util/limbo_utility.cpp b/util/limbo_utility.cpp index 087dcd0..ed2f64f 100644 --- a/util/limbo_utility.cpp +++ b/util/limbo_utility.cpp @@ -288,6 +288,129 @@ Variant LimboUtility::perform_operation(Operation p_operation, const Variant &le return ret; } +String LimboUtility::get_property_hint_text(PropertyHint p_hint) const { + switch (p_hint) { + case PROPERTY_HINT_NONE: { + return "NONE"; + } + case PROPERTY_HINT_RANGE: { + return "RANGE"; + } + case PROPERTY_HINT_ENUM: { + return "ENUM"; + } + case PROPERTY_HINT_ENUM_SUGGESTION: { + return "SUGGESTION"; + } + case PROPERTY_HINT_EXP_EASING: { + return "EXP_EASING"; + } + case PROPERTY_HINT_LINK: { + return "LINK"; + } + case PROPERTY_HINT_FLAGS: { + return "FLAGS"; + } + case PROPERTY_HINT_LAYERS_2D_RENDER: { + return "LAYERS_2D_RENDER"; + } + case PROPERTY_HINT_LAYERS_2D_PHYSICS: { + return "LAYERS_2D_PHYSICS"; + } + case PROPERTY_HINT_LAYERS_2D_NAVIGATION: { + return "LAYERS_2D_NAVIGATION"; + } + case PROPERTY_HINT_LAYERS_3D_RENDER: { + return "LAYERS_3D_RENDER"; + } + case PROPERTY_HINT_LAYERS_3D_PHYSICS: { + return "LAYERS_3D_PHYSICS"; + } + case PROPERTY_HINT_LAYERS_3D_NAVIGATION: { + return "LAYERS_3D_NAVIGATION"; + } + case PROPERTY_HINT_FILE: { + return "FILE"; + } + case PROPERTY_HINT_DIR: { + return "DIR"; + } + case PROPERTY_HINT_GLOBAL_FILE: { + return "GLOBAL_FILE"; + } + case PROPERTY_HINT_GLOBAL_DIR: { + return "GLOBAL_DIR"; + } + case PROPERTY_HINT_RESOURCE_TYPE: { + return "RESOURCE_TYPE"; + } + case PROPERTY_HINT_MULTILINE_TEXT: { + return "MULTILINE_TEXT"; + } + case PROPERTY_HINT_EXPRESSION: { + return "EXPRESSION"; + } + case PROPERTY_HINT_PLACEHOLDER_TEXT: { + return "PLACEHOLDER_TEXT"; + } + case PROPERTY_HINT_COLOR_NO_ALPHA: { + return "COLOR_NO_ALPHA"; + } + case PROPERTY_HINT_OBJECT_ID: { + return "OBJECT_ID"; + } + case PROPERTY_HINT_TYPE_STRING: { + return "TYPE_STRING"; + } + case PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE: { + return "NODE_PATH_TO_EDITED_NODE"; + } + case PROPERTY_HINT_OBJECT_TOO_BIG: { + return "OBJECT_TOO_BIG"; + } + case PROPERTY_HINT_NODE_PATH_VALID_TYPES: { + return "NODE_PATH_VALID_TYPES"; + } + case PROPERTY_HINT_SAVE_FILE: { + return "SAVE_FILE"; + } + case PROPERTY_HINT_GLOBAL_SAVE_FILE: { + return "GLOBAL_SAVE_FILE"; + } + case PROPERTY_HINT_INT_IS_OBJECTID: { + return "INT_IS_OBJECTID"; + } + case PROPERTY_HINT_INT_IS_POINTER: { + return "INT_IS_POINTER"; + } + case PROPERTY_HINT_ARRAY_TYPE: { + return "ARRAY_TYPE"; + } + case PROPERTY_HINT_LOCALE_ID: { + return "LOCALE_ID"; + } + case PROPERTY_HINT_LOCALIZABLE_STRING: { + return "LOCALIZABLE_STRING"; + } + case PROPERTY_HINT_NODE_TYPE: { + return "NODE_TYPE"; + } + case PROPERTY_HINT_HIDE_QUATERNION_EDIT: { + return "HIDE_QUATERNION_EDIT"; + } + case PROPERTY_HINT_PASSWORD: { + return "PASSWORD"; + } + case PROPERTY_HINT_LAYERS_AVOIDANCE: { + return "LAYERS_AVOIDANCE"; + } + case PROPERTY_HINT_MAX: { + return "MAX"; + } + } + return ""; +} + #ifdef TOOLS_ENABLED Ref LimboUtility::add_shortcut(const String &p_path, const String &p_name, Key p_keycode) { diff --git a/util/limbo_utility.h b/util/limbo_utility.h index bcb5a1d..39045a4 100644 --- a/util/limbo_utility.h +++ b/util/limbo_utility.h @@ -88,6 +88,8 @@ public: String get_operation_string(Operation p_operation) const; Variant perform_operation(Operation p_operation, const Variant &left_value, const Variant &right_value); + String get_property_hint_text(PropertyHint p_hint) const; + #ifdef TOOLS_ENABLED Ref add_shortcut(const String &p_path, const String &p_name, Key p_keycode = LW_KEY(NONE)); bool is_shortcut(const String &p_path, const Ref &p_event) const;