Merge pull request #245 from limbonaut/editor-prop-binding
Feature: Bind `BlackboardPlan` variables to node properties in the Inspector
This commit is contained in:
commit
ba90deaa6a
|
@ -13,9 +13,20 @@
|
|||
|
||||
#include "../util/limbo_utility.h"
|
||||
|
||||
#ifdef LIMBOAI_MODULE
|
||||
#include "editor/editor_inspector.h"
|
||||
#include "editor/editor_interface.h"
|
||||
#elif LIMBOAI_GDEXTENSION
|
||||
#include <godot_cpp/classes/editor_inspector.hpp>
|
||||
#include <godot_cpp/classes/editor_interface.hpp>
|
||||
#include <godot_cpp/classes/engine.hpp>
|
||||
#include <godot_cpp/classes/scene_tree.hpp>
|
||||
#endif
|
||||
|
||||
bool BlackboardPlan::_set(const StringName &p_name, const Variant &p_value) {
|
||||
String name_str = p_name;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
// * Editor
|
||||
if (var_map.has(p_name)) {
|
||||
BBVariable &var = var_map[p_name];
|
||||
|
@ -26,29 +37,51 @@ bool BlackboardPlan::_set(const StringName &p_name, const Variant &p_value) {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
// * Mapping
|
||||
if (name_str.begins_with("mapping/")) {
|
||||
StringName mapped_var_name = name_str.get_slicec('/', 1);
|
||||
StringName value = p_value;
|
||||
bool properties_changed = false;
|
||||
bool prop_list_changed = false;
|
||||
if (value == StringName()) {
|
||||
if (parent_scope_mapping.has(mapped_var_name)) {
|
||||
properties_changed = true;
|
||||
prop_list_changed = true;
|
||||
parent_scope_mapping.erase(mapped_var_name);
|
||||
}
|
||||
} else {
|
||||
if (!parent_scope_mapping.has(mapped_var_name)) {
|
||||
properties_changed = true;
|
||||
prop_list_changed = true;
|
||||
}
|
||||
parent_scope_mapping[mapped_var_name] = value;
|
||||
}
|
||||
if (properties_changed) {
|
||||
if (prop_list_changed) {
|
||||
notify_property_list_changed();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// * Binding
|
||||
if (name_str.begins_with("binding/")) {
|
||||
StringName bound_var = name_str.get_slicec('/', 1);
|
||||
NodePath value = p_value;
|
||||
bool prop_list_changed = false;
|
||||
if (value.is_empty()) {
|
||||
if (property_bindings.has(bound_var)) {
|
||||
prop_list_changed = true;
|
||||
property_bindings.erase(bound_var);
|
||||
}
|
||||
} else {
|
||||
if (!property_bindings.has(bound_var)) {
|
||||
prop_list_changed = true;
|
||||
}
|
||||
property_bindings[bound_var] = value;
|
||||
}
|
||||
if (prop_list_changed) {
|
||||
notify_property_list_changed();
|
||||
}
|
||||
}
|
||||
|
||||
// * Storage
|
||||
if (name_str.begins_with("var/")) {
|
||||
StringName var_name = name_str.get_slicec('/', 1);
|
||||
|
@ -66,6 +99,8 @@ bool BlackboardPlan::_set(const StringName &p_name, const Variant &p_value) {
|
|||
var_map[var_name].set_hint((PropertyHint)(int)p_value);
|
||||
} else if (what == "hint_string") {
|
||||
var_map[var_name].set_hint_string(p_value);
|
||||
} else if (what == "property_binding") {
|
||||
property_bindings[var_name] = NodePath(p_value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@ -78,28 +113,64 @@ bool BlackboardPlan::_set(const StringName &p_name, const Variant &p_value) {
|
|||
bool BlackboardPlan::_get(const StringName &p_name, Variant &r_ret) const {
|
||||
String name_str = p_name;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
// * Editor
|
||||
if (var_map.has(p_name)) {
|
||||
if (has_mapping(p_name)) {
|
||||
r_ret = "Mapped to " + LimboUtility::get_singleton()->decorate_var(parent_scope_mapping[p_name]);
|
||||
} else if (has_property_binding(p_name)) {
|
||||
const NodePath &binding = property_bindings[p_name];
|
||||
|
||||
Node *edited_node = Object::cast_to<Node>(EditorInterface::get_singleton()->get_inspector()->get_edited_object());
|
||||
if (!edited_node) {
|
||||
edited_node = SCENE_TREE()->get_edited_scene_root();
|
||||
}
|
||||
Node *bound_node = edited_node ? edited_node->get_node_or_null(binding) : nullptr;
|
||||
|
||||
String shortened_path;
|
||||
if (bound_node) {
|
||||
shortened_path = (String)bound_node->get_name() +
|
||||
":" + (String)binding.get_concatenated_subnames();
|
||||
} else {
|
||||
shortened_path = (String)binding.get_name(binding.get_name_count() - 1) +
|
||||
":" + (String)binding.get_concatenated_subnames();
|
||||
}
|
||||
r_ret = String::utf8("🔗 ") + shortened_path;
|
||||
} else {
|
||||
r_ret = var_map[p_name].get_value();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
// * Mapping
|
||||
if (name_str.begins_with("mapping/")) {
|
||||
StringName mapped_var_name = name_str.get_slicec('/', 1);
|
||||
ERR_FAIL_COND_V(mapped_var_name == StringName(), false);
|
||||
if (parent_scope_mapping.has(mapped_var_name)) {
|
||||
if (has_mapping(mapped_var_name)) {
|
||||
r_ret = parent_scope_mapping[mapped_var_name];
|
||||
} else if (has_property_binding(mapped_var_name)) {
|
||||
r_ret = RTR("Already bound to property.");
|
||||
} else {
|
||||
r_ret = StringName();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// * Binding
|
||||
if (name_str.begins_with("binding/")) {
|
||||
StringName bound_var = name_str.get_slicec('/', 1);
|
||||
ERR_FAIL_COND_V(bound_var == StringName(), false);
|
||||
if (has_property_binding(bound_var)) {
|
||||
r_ret = property_bindings[bound_var];
|
||||
} else if (has_mapping(bound_var)) {
|
||||
r_ret = RTR("Already mapped to variable.");
|
||||
} else {
|
||||
r_ret = NodePath();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// * Storage
|
||||
if (!name_str.begins_with("var/")) {
|
||||
return false;
|
||||
|
@ -127,14 +198,16 @@ void BlackboardPlan::_get_property_list(List<PropertyInfo> *p_list) const {
|
|||
String var_name = p.first;
|
||||
BBVariable var = p.second;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
// * Editor
|
||||
if (var.get_type() != Variant::NIL && (!is_derived() || !var_name.begins_with("_"))) {
|
||||
if (has_mapping(var_name)) {
|
||||
if (!_is_var_hidden(var_name, var)) {
|
||||
if (has_mapping(var_name) || has_property_binding(var_name)) {
|
||||
p_list->push_back(PropertyInfo(Variant::STRING, var_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY));
|
||||
} else {
|
||||
p_list->push_back(PropertyInfo(var.get_type(), var_name, var.get_hint(), var.get_hint_string(), PROPERTY_USAGE_EDITOR));
|
||||
}
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
// * Storage
|
||||
if (is_derived() && (!var.is_value_changed() || var.get_value() == base->var_map[var_name].get_value())) {
|
||||
|
@ -153,6 +226,12 @@ void BlackboardPlan::_get_property_list(List<PropertyInfo> *p_list) const {
|
|||
if (is_mapping_enabled()) {
|
||||
p_list->push_back(PropertyInfo(Variant::NIL, "Mapping", PROPERTY_HINT_NONE, "mapping/", PROPERTY_USAGE_GROUP));
|
||||
for (const Pair<StringName, BBVariable> &p : var_list) {
|
||||
if (_is_var_hidden(p.first, p.second)) {
|
||||
continue;
|
||||
}
|
||||
if (unlikely(has_property_binding(p.first))) {
|
||||
p_list->push_back(PropertyInfo(Variant::STRING, "mapping/" + p.first, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY));
|
||||
} else {
|
||||
// Serialize only non-empty mappings.
|
||||
PropertyUsageFlags usage = has_mapping(p.first) ? PROPERTY_USAGE_DEFAULT : PROPERTY_USAGE_EDITOR;
|
||||
p_list->push_back(PropertyInfo(Variant::STRING_NAME, "mapping/" + p.first, PROPERTY_HINT_NONE, "", usage));
|
||||
|
@ -160,6 +239,23 @@ void BlackboardPlan::_get_property_list(List<PropertyInfo> *p_list) const {
|
|||
}
|
||||
}
|
||||
|
||||
// * Binding
|
||||
p_list->push_back(PropertyInfo(Variant::NIL, "Binding", PROPERTY_HINT_NONE, "binding/", PROPERTY_USAGE_GROUP));
|
||||
for (const Pair<StringName, BBVariable> &p : var_list) {
|
||||
if (_is_var_hidden(p.first, p.second)) {
|
||||
continue;
|
||||
}
|
||||
if (unlikely(has_mapping(p.first))) {
|
||||
p_list->push_back(PropertyInfo(Variant::STRING, "binding/" + p.first, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY));
|
||||
} else {
|
||||
PropertyUsageFlags usage = has_property_binding(p.first) ? PROPERTY_USAGE_DEFAULT : PROPERTY_USAGE_EDITOR;
|
||||
// PROPERTY_HINT_LINK is used to signal that NodePath should point to a property.
|
||||
// Our inspector plugin will know how to handle it.
|
||||
p_list->push_back(PropertyInfo(Variant::NODE_PATH, "binding/" + p.first, PROPERTY_HINT_LINK, itos(p.second.get_type()), usage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BlackboardPlan::_property_can_revert(const StringName &p_name) const {
|
||||
if (String(p_name).begins_with("mapping/")) {
|
||||
return true;
|
||||
|
@ -199,6 +295,11 @@ bool BlackboardPlan::has_mapping(const StringName &p_name) const {
|
|||
return is_mapping_enabled() && parent_scope_mapping.has(p_name) && parent_scope_mapping[p_name] != StringName();
|
||||
}
|
||||
|
||||
void BlackboardPlan::set_property_binding(const StringName &p_name, const NodePath &p_path) {
|
||||
property_bindings[p_name] = p_path;
|
||||
emit_changed();
|
||||
}
|
||||
|
||||
void BlackboardPlan::set_prefetch_nodepath_vars(bool p_enable) {
|
||||
prefetch_nodepath_vars = p_enable;
|
||||
emit_changed();
|
||||
|
@ -410,8 +511,9 @@ void BlackboardPlan::populate_blackboard(const Ref<Blackboard> &p_blackboard, bo
|
|||
#endif
|
||||
continue;
|
||||
}
|
||||
bool is_bound = has_property_binding(p.first) || (is_derived() && get_base_plan()->has_property_binding(p.first));
|
||||
bool has_mapping = parent_scope_mapping.has(p.first);
|
||||
bool do_prefetch = !has_mapping && prefetch_nodepath_vars;
|
||||
bool do_prefetch = !is_bound && !has_mapping && prefetch_nodepath_vars;
|
||||
|
||||
// Add a variable duplicate to the blackboard, optionally with NodePath prefetch.
|
||||
BBVariable var = p.second.duplicate(true);
|
||||
|
@ -433,6 +535,24 @@ void BlackboardPlan::populate_blackboard(const Ref<Blackboard> &p_blackboard, bo
|
|||
ERR_CONTINUE_MSG(p_blackboard->get_parent() == nullptr, vformat("BlackboardPlan: Cannot link variable %s to parent scope because the parent scope is not set.", LimboUtility::get_singleton()->decorate_var(p.first)));
|
||||
p_blackboard->link_var(p.first, p_blackboard->get_parent(), target_var);
|
||||
}
|
||||
} else if (is_bound) {
|
||||
// Bind variable to a property of a scene node.
|
||||
NodePath binding_path;
|
||||
Node *binding_root;
|
||||
if (has_property_binding(p.first)) {
|
||||
binding_path = property_bindings[p.first];
|
||||
binding_root = p_prefetch_root;
|
||||
} else {
|
||||
binding_path = get_base_plan()->property_bindings[p.first];
|
||||
binding_root = p_prefetch_root_for_base_plan;
|
||||
}
|
||||
ERR_CONTINUE_MSG(binding_path.get_subname_count() != 1, vformat("BlackboardPlan: Can't bind variable %s using property path that contains multiple sub-names: %s", LimboUtility::get_singleton()->decorate_var(p.first), binding_path));
|
||||
NodePath node_path{ binding_path.get_concatenated_names() };
|
||||
StringName prop_name = binding_path.get_subname(0);
|
||||
// TODO: Implement binding for base plan as well.
|
||||
Node *n = binding_root->get_node_or_null(node_path);
|
||||
ERR_CONTINUE_MSG(n == nullptr, vformat("BlackboardPlan: Binding failed for variable %s using property path: %s", LimboUtility::get_singleton()->decorate_var(p.first), binding_path));
|
||||
var.bind(n, prop_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,18 +34,25 @@ private:
|
|||
// 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,
|
||||
// and only the values can be different in those variables.
|
||||
// The derived plan is synced with the base plan to maintain consistency.
|
||||
Ref<BlackboardPlan> base;
|
||||
|
||||
// Mapping between variables in this plan and their parent scope names.
|
||||
// Used for linking variables to their parent scope counterparts upon Blackboard creation/population.
|
||||
HashMap<StringName, StringName> parent_scope_mapping;
|
||||
// Fetcher function for the parent scope plan. Funtion should return a Ref<BlackboardPlan>.
|
||||
// Used in the inspector. When set, mapping feature becomes available.
|
||||
// Fetcher function for the parent scope plan. Function should return a Ref<BlackboardPlan>.
|
||||
// Used in the inspector: enables mapping feature when set.
|
||||
Callable parent_scope_plan_provider;
|
||||
|
||||
// Bindings to properties in the scene to which this plan belongs.
|
||||
HashMap<StringName, NodePath> property_bindings;
|
||||
bool property_binding_enabled = false;
|
||||
|
||||
// If true, NodePath variables will be prefetched, so that the vars will contain node pointers instead (upon BB creation/population).
|
||||
bool prefetch_nodepath_vars = true;
|
||||
|
||||
_FORCE_INLINE_ bool _is_var_hidden(const String &p_name, const BBVariable &p_var) const { return p_var.get_type() == Variant::NIL || (is_derived() && p_name.begins_with("_")); }
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
|
@ -69,6 +76,10 @@ public:
|
|||
bool is_mapping_enabled() const { return parent_scope_plan_provider.is_valid() && (parent_scope_plan_provider.call() != Ref<BlackboardPlan>()); }
|
||||
bool has_mapping(const StringName &p_name) const;
|
||||
|
||||
bool has_property_binding(const StringName &p_name) const { return property_bindings.has(p_name); }
|
||||
void set_property_binding(const StringName &p_name, const NodePath &p_path);
|
||||
NodePath get_property_binding(const StringName &p_name) const { return property_bindings.has(p_name) ? property_bindings[p_name] : NodePath(); }
|
||||
|
||||
void set_prefetch_nodepath_vars(bool p_enable);
|
||||
bool is_prefetching_nodepath_vars() const;
|
||||
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
/**
|
||||
* editor_property_path.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 "editor_property_property_path.h"
|
||||
|
||||
#include "../util/limbo_compat.h"
|
||||
#include "../util/limbo_string_names.h"
|
||||
|
||||
#ifdef LIMBOAI_MODULE
|
||||
#include "editor/editor_data.h"
|
||||
#include "editor/editor_interface.h"
|
||||
#include "servers/display_server.h"
|
||||
#endif
|
||||
|
||||
#ifdef LIMBOAI_GDEXTENSION
|
||||
#include <godot_cpp/classes/display_server.hpp>
|
||||
#include <godot_cpp/classes/editor_inspector.hpp>
|
||||
#include <godot_cpp/classes/editor_interface.hpp>
|
||||
#include <godot_cpp/classes/editor_selection.hpp>
|
||||
#include <godot_cpp/classes/h_box_container.hpp>
|
||||
#include <godot_cpp/classes/popup_menu.hpp>
|
||||
#include <godot_cpp/classes/scene_tree.hpp>
|
||||
#endif // LIMBOAI_MODULE
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
namespace {
|
||||
|
||||
Node *_get_base_node(Object *p_edited_object, SceneTree *p_scene_tree) {
|
||||
Node *base_node = Object::cast_to<Node>(p_edited_object);
|
||||
if (!base_node) {
|
||||
base_node = Object::cast_to<Node>(EditorInterface::get_singleton()->get_inspector()->get_edited_object());
|
||||
}
|
||||
if (!base_node) {
|
||||
base_node = p_scene_tree->get_edited_scene_root();
|
||||
}
|
||||
return base_node;
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
Node *EditorPropertyPropertyPath::_get_selected_node() {
|
||||
ERR_FAIL_NULL_V(get_edited_object(), nullptr);
|
||||
|
||||
NodePath path = get_edited_object()->get(get_edited_property());
|
||||
if (path.is_empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Node *base_node = _get_base_node(get_edited_object(), get_tree());
|
||||
ERR_FAIL_NULL_V(base_node, nullptr);
|
||||
Node *selected_node = base_node->get_node_or_null(path);
|
||||
return selected_node;
|
||||
}
|
||||
|
||||
void EditorPropertyPropertyPath::_action_selected(int p_idx) {
|
||||
switch (p_idx) {
|
||||
case ACTION_CLEAR: {
|
||||
emit_changed(get_edited_property(), NodePath());
|
||||
} break;
|
||||
case ACTION_COPY: {
|
||||
DisplayServer::get_singleton()->clipboard_set(get_edited_object()->get(get_edited_property()));
|
||||
} break;
|
||||
case ACTION_EDIT: {
|
||||
assign_button->hide();
|
||||
action_menu->hide();
|
||||
path_edit->show();
|
||||
path_edit->set_text(get_edited_object()->get(get_edited_property()));
|
||||
path_edit->grab_focus();
|
||||
} break;
|
||||
case ACTION_SELECT: {
|
||||
Node *selected_node = _get_selected_node();
|
||||
if (selected_node) {
|
||||
EditorInterface::get_singleton()->get_selection()->clear();
|
||||
EditorInterface::get_singleton()->get_selection()->add_node(selected_node);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPropertyPropertyPath::_accept_text() {
|
||||
path_edit->hide();
|
||||
assign_button->show();
|
||||
action_menu->show();
|
||||
emit_changed(get_edited_property(), path_edit->get_text());
|
||||
}
|
||||
|
||||
void EditorPropertyPropertyPath::_property_selected(const NodePath &p_property_path, const NodePath &p_node_path) {
|
||||
if (p_property_path.is_empty()) {
|
||||
return;
|
||||
}
|
||||
Node *base_node = _get_base_node(get_edited_object(), get_tree());
|
||||
ERR_FAIL_NULL(base_node);
|
||||
Node *selected_node = get_tree()->get_edited_scene_root()->get_node_or_null(p_node_path);
|
||||
ERR_FAIL_NULL(selected_node);
|
||||
NodePath path = String(base_node->get_path_to(selected_node)) + String(p_property_path);
|
||||
|
||||
emit_changed(get_edited_property(), path);
|
||||
update_property();
|
||||
}
|
||||
|
||||
void EditorPropertyPropertyPath::_node_selected(const NodePath &p_path) {
|
||||
if (p_path.is_empty()) {
|
||||
return;
|
||||
}
|
||||
Node *selected_node = get_tree()->get_edited_scene_root()->get_node_or_null(p_path);
|
||||
ERR_FAIL_NULL(selected_node);
|
||||
EditorInterface::get_singleton()->popup_property_selector(
|
||||
selected_node,
|
||||
callable_mp(this, &EditorPropertyPropertyPath::_property_selected).bind(p_path),
|
||||
valid_types);
|
||||
}
|
||||
|
||||
void EditorPropertyPropertyPath::_choose_property() {
|
||||
EditorInterface::get_singleton()->popup_node_selector(callable_mp(this, &EditorPropertyPropertyPath::_node_selected));
|
||||
}
|
||||
|
||||
void EditorPropertyPropertyPath::_set_read_only(bool p_read_only) {
|
||||
assign_button->set_disabled(p_read_only);
|
||||
action_menu->set_disabled(p_read_only);
|
||||
}
|
||||
|
||||
#ifdef LIMBOAI_MODULE
|
||||
void EditorPropertyPropertyPath::update_property() {
|
||||
#elif LIMBOAI_GDEXTENSION
|
||||
void EditorPropertyPropertyPath::_update_property() {
|
||||
#endif
|
||||
NodePath path = get_edited_object()->get(get_edited_property());
|
||||
if (path.is_empty()) {
|
||||
assign_button->set_text(TTR("Bind..."));
|
||||
} else {
|
||||
Node *base_node = _get_base_node(get_edited_object(), get_tree());
|
||||
ERR_FAIL_NULL(base_node);
|
||||
Node *selected_node = base_node->get_node_or_null(path);
|
||||
String text;
|
||||
if (selected_node) {
|
||||
text = (String)selected_node->get_name() +
|
||||
":" + (String)path.get_concatenated_subnames();
|
||||
} else {
|
||||
text = (String)path;
|
||||
}
|
||||
assign_button->set_text(text);
|
||||
assign_button->set_tooltip_text(path);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPropertyPropertyPath::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
BUTTON_SET_ICON(action_menu, get_theme_icon(LW_NAME(GuiTabMenuHl), LW_NAME(EditorIcons)));
|
||||
action_menu->get_popup()->set_item_icon(ACTION_CLEAR, get_theme_icon(LW_NAME(Clear), LW_NAME(EditorIcons)));
|
||||
action_menu->get_popup()->set_item_icon(ACTION_COPY, get_theme_icon(LW_NAME(ActionCopy), LW_NAME(EditorIcons)));
|
||||
action_menu->get_popup()->set_item_icon(ACTION_EDIT, get_theme_icon(LW_NAME(Edit), LW_NAME(EditorIcons)));
|
||||
action_menu->get_popup()->set_item_icon(ACTION_SELECT, get_theme_icon(LW_NAME(ExternalLink), LW_NAME(EditorIcons)));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPropertyPropertyPath::setup(const PackedInt32Array &p_valid_types) {
|
||||
valid_types = p_valid_types;
|
||||
}
|
||||
|
||||
EditorPropertyPropertyPath::EditorPropertyPropertyPath() {
|
||||
HBoxContainer *hb = memnew(HBoxContainer);
|
||||
add_child(hb);
|
||||
hb->add_theme_constant_override(LW_NAME(separation), 0);
|
||||
|
||||
assign_button = memnew(Button);
|
||||
hb->add_child(assign_button);
|
||||
assign_button->set_flat(true);
|
||||
assign_button->set_text(TTR("Bind..."));
|
||||
assign_button->set_clip_text(true);
|
||||
assign_button->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
assign_button->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
assign_button->connect(LW_NAME(pressed), callable_mp(this, &EditorPropertyPropertyPath::_choose_property));
|
||||
|
||||
path_edit = memnew(LineEdit);
|
||||
hb->add_child(path_edit);
|
||||
path_edit->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
path_edit->connect(LW_NAME(focus_exited), callable_mp(this, &EditorPropertyPropertyPath::_accept_text));
|
||||
path_edit->connect(LW_NAME(text_submitted), callable_mp(this, &EditorPropertyPropertyPath::_accept_text).unbind(1));
|
||||
path_edit->hide();
|
||||
|
||||
action_menu = memnew(MenuButton);
|
||||
action_menu->get_popup()->add_item(TTR("Clear"), ACTION_CLEAR);
|
||||
action_menu->get_popup()->add_item(TTR("Copy as Text"), ACTION_COPY);
|
||||
action_menu->get_popup()->add_item(TTR("Edit"), ACTION_EDIT);
|
||||
action_menu->get_popup()->add_item(TTR("Show Node in Tree"), ACTION_SELECT);
|
||||
action_menu->get_popup()->connect(LW_NAME(id_pressed), callable_mp(this, &EditorPropertyPropertyPath::_action_selected));
|
||||
hb->add_child(action_menu);
|
||||
}
|
||||
|
||||
//***** EditorInspectorPluginPropertyPath
|
||||
|
||||
#ifdef LIMBOAI_MODULE
|
||||
bool EditorInspectorPluginPropertyPath::can_handle(Object *p_object) {
|
||||
#elif LIMBOAI_GDEXTENSION
|
||||
bool EditorInspectorPluginPropertyPath::_can_handle(Object *p_object) const {
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef LIMBOAI_MODULE
|
||||
bool EditorInspectorPluginPropertyPath::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {
|
||||
#elif LIMBOAI_GDEXTENSION
|
||||
bool EditorInspectorPluginPropertyPath::_parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {
|
||||
#endif
|
||||
if (p_type != Variant::NODE_PATH || p_hint != PROPERTY_HINT_LINK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EditorPropertyPropertyPath *ed = memnew(EditorPropertyPropertyPath);
|
||||
|
||||
// Convert the hint text to an array of valid types.
|
||||
PackedInt32Array valid_types;
|
||||
PackedStringArray type_specifiers = p_hint_text.split(",");
|
||||
for (const String &t : type_specifiers) {
|
||||
if (t.is_valid_int()) {
|
||||
valid_types.append(t.to_int());
|
||||
}
|
||||
}
|
||||
ed->setup(valid_types);
|
||||
|
||||
add_property_editor(p_path, ed);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // TOOLS_ENABLED
|
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* editor_property_path.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 EDITOR_PROPERTY_PROPERTY_PATH
|
||||
#define EDITOR_PROPERTY_PROPERTY_PATH
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
#ifdef LIMBOAI_MODULE
|
||||
#include "editor/editor_inspector.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
#include "scene/gui/menu_button.h"
|
||||
#endif // LIMBOAI_MODULE
|
||||
|
||||
#ifdef LIMBOAI_GDEXTENSION
|
||||
#include <godot_cpp/classes/button.hpp>
|
||||
#include <godot_cpp/classes/editor_inspector_plugin.hpp>
|
||||
#include <godot_cpp/classes/editor_property.hpp>
|
||||
#include <godot_cpp/classes/line_edit.hpp>
|
||||
#include <godot_cpp/classes/menu_button.hpp>
|
||||
using namespace godot;
|
||||
#endif // LIMBOAI_GDEXTENSION
|
||||
|
||||
// Specialized property editor for NodePath properties that represent a path to a specific property instead of just a node.
|
||||
// Handles NodePath properties that have PROPERTY_HINT_LINK.
|
||||
// Hint string can list the valid Variant types as comma-separated integers.
|
||||
class EditorPropertyPropertyPath : public EditorProperty {
|
||||
GDCLASS(EditorPropertyPropertyPath, EditorProperty);
|
||||
|
||||
private:
|
||||
enum Action {
|
||||
ACTION_CLEAR,
|
||||
ACTION_COPY,
|
||||
ACTION_EDIT,
|
||||
ACTION_SELECT,
|
||||
};
|
||||
|
||||
Button *assign_button;
|
||||
MenuButton *action_menu;
|
||||
LineEdit *path_edit;
|
||||
|
||||
PackedInt32Array valid_types;
|
||||
|
||||
Node *_get_selected_node();
|
||||
void _action_selected(int p_idx);
|
||||
void _accept_text();
|
||||
void _property_selected(const NodePath &p_property_path, const NodePath &p_node_path);
|
||||
void _node_selected(const NodePath &p_path);
|
||||
void _choose_property();
|
||||
|
||||
protected:
|
||||
static void _bind_methods() {}
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
// Note: Needs to be public in GDExtension.
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
|
||||
#ifdef LIMBOAI_MODULE
|
||||
virtual void update_property() override;
|
||||
#elif LIMBOAI_GDEXTENSION
|
||||
virtual void _update_property() override;
|
||||
#endif
|
||||
|
||||
void setup(const PackedInt32Array &p_valid_types);
|
||||
EditorPropertyPropertyPath();
|
||||
};
|
||||
|
||||
class EditorInspectorPluginPropertyPath : public EditorInspectorPlugin {
|
||||
GDCLASS(EditorInspectorPluginPropertyPath, EditorInspectorPlugin);
|
||||
|
||||
private:
|
||||
protected:
|
||||
static void _bind_methods() {}
|
||||
|
||||
public:
|
||||
#ifdef LIMBOAI_MODULE
|
||||
virtual bool can_handle(Object *p_object) override;
|
||||
virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide = false) override;
|
||||
#elif LIMBOAI_GDEXTENSION
|
||||
virtual bool _can_handle(Object *p_object) const override;
|
||||
virtual bool _parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide = false) override;
|
||||
#endif
|
||||
|
||||
EditorInspectorPluginPropertyPath() = default;
|
||||
};
|
||||
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
#endif // EDITOR_PROPERTY_PROPERTY_PATH
|
|
@ -245,6 +245,11 @@ bool EditorInspectorPluginVariableName::parse_property(Object *p_object, const V
|
|||
#elif LIMBOAI_GDEXTENSION
|
||||
bool EditorInspectorPluginVariableName::_parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {
|
||||
#endif
|
||||
if (p_usage & PROPERTY_USAGE_READ_ONLY) {
|
||||
// Don't handle read-only properties using this plugin.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_mapping = p_path.begins_with("mapping/");
|
||||
if (!(p_type == Variant::Type::STRING_NAME || p_type == Variant::Type::STRING) || !(is_mapping || p_path.ends_with("_var") || p_path.ends_with("variable"))) {
|
||||
return false;
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "blackboard_plan_editor.h"
|
||||
#include "debugger/limbo_debugger_plugin.h"
|
||||
#include "editor_property_bb_param.h"
|
||||
#include "editor_property_property_path.h"
|
||||
|
||||
#ifdef LIMBOAI_MODULE
|
||||
#include "core/config/project_settings.h"
|
||||
|
@ -1918,9 +1919,13 @@ void LimboAIEditorPlugin::_notification(int p_notification) {
|
|||
case NOTIFICATION_READY: {
|
||||
add_debugger_plugin(memnew(LimboDebuggerPlugin));
|
||||
add_inspector_plugin(memnew(EditorInspectorPluginBBPlan));
|
||||
|
||||
EditorInspectorPluginVariableName *var_plugin = memnew(EditorInspectorPluginVariableName);
|
||||
var_plugin->set_editor_plan_provider(Callable(limbo_ai_editor, "get_edited_blackboard_plan"));
|
||||
add_inspector_plugin(var_plugin);
|
||||
|
||||
EditorInspectorPluginPropertyPath *path_plugin = memnew(EditorInspectorPluginPropertyPath);
|
||||
add_inspector_plugin(path_plugin);
|
||||
#ifdef LIMBOAI_MODULE
|
||||
// ! Only used in the module version.
|
||||
EditorInspectorPluginBBParam *param_plugin = memnew(EditorInspectorPluginBBParam);
|
||||
|
|
|
@ -120,6 +120,7 @@
|
|||
#endif // LIMBOAI_MODULE
|
||||
|
||||
#ifdef LIMBOAI_GDEXTENSION
|
||||
#include "editor/editor_property_property_path.h"
|
||||
#include <godot_cpp/classes/engine.hpp>
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
#include <godot_cpp/core/memory.hpp>
|
||||
|
@ -250,24 +251,26 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) {
|
|||
#ifdef TOOLS_ENABLED
|
||||
if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {
|
||||
#ifdef LIMBOAI_GDEXTENSION
|
||||
GDREGISTER_CLASS(TaskTree);
|
||||
GDREGISTER_CLASS(TaskButton);
|
||||
GDREGISTER_CLASS(TaskPaletteSection);
|
||||
GDREGISTER_CLASS(TaskPalette);
|
||||
GDREGISTER_CLASS(ActionBanner);
|
||||
GDREGISTER_CLASS(ModeSwitchButton);
|
||||
GDREGISTER_CLASS(CompatShortcutBin);
|
||||
GDREGISTER_CLASS(CompatScreenSelect);
|
||||
GDREGISTER_CLASS(CompatWindowWrapper);
|
||||
GDREGISTER_CLASS(LimboDebuggerTab);
|
||||
GDREGISTER_CLASS(LimboDebuggerPlugin);
|
||||
GDREGISTER_CLASS(BlackboardPlanEditor);
|
||||
GDREGISTER_CLASS(EditorInspectorPluginBBPlan);
|
||||
GDREGISTER_CLASS(EditorPropertyVariableName);
|
||||
GDREGISTER_CLASS(EditorInspectorPluginVariableName);
|
||||
GDREGISTER_CLASS(OwnerPicker);
|
||||
GDREGISTER_CLASS(LimboAIEditor);
|
||||
GDREGISTER_CLASS(LimboAIEditorPlugin);
|
||||
GDREGISTER_INTERNAL_CLASS(TaskTree);
|
||||
GDREGISTER_INTERNAL_CLASS(TaskButton);
|
||||
GDREGISTER_INTERNAL_CLASS(TaskPaletteSection);
|
||||
GDREGISTER_INTERNAL_CLASS(TaskPalette);
|
||||
GDREGISTER_INTERNAL_CLASS(ActionBanner);
|
||||
GDREGISTER_INTERNAL_CLASS(ModeSwitchButton);
|
||||
GDREGISTER_INTERNAL_CLASS(CompatShortcutBin);
|
||||
GDREGISTER_INTERNAL_CLASS(CompatScreenSelect);
|
||||
GDREGISTER_INTERNAL_CLASS(CompatWindowWrapper);
|
||||
GDREGISTER_INTERNAL_CLASS(LimboDebuggerTab);
|
||||
GDREGISTER_INTERNAL_CLASS(LimboDebuggerPlugin);
|
||||
GDREGISTER_INTERNAL_CLASS(BlackboardPlanEditor);
|
||||
GDREGISTER_INTERNAL_CLASS(EditorInspectorPluginBBPlan);
|
||||
GDREGISTER_INTERNAL_CLASS(EditorInspectorPluginPropertyPath);
|
||||
GDREGISTER_INTERNAL_CLASS(EditorPropertyPropertyPath);
|
||||
GDREGISTER_INTERNAL_CLASS(EditorPropertyVariableName);
|
||||
GDREGISTER_INTERNAL_CLASS(EditorInspectorPluginVariableName);
|
||||
GDREGISTER_INTERNAL_CLASS(OwnerPicker);
|
||||
GDREGISTER_INTERNAL_CLASS(LimboAIEditor);
|
||||
GDREGISTER_INTERNAL_CLASS(LimboAIEditorPlugin);
|
||||
GDREGISTER_INTERNAL_CLASS(TreeSearchPanel);
|
||||
GDREGISTER_INTERNAL_CLASS(TreeSearch);
|
||||
#endif // LIMBOAI_GDEXTENSION
|
||||
|
|
|
@ -152,6 +152,7 @@ Variant _GLOBAL_DEF(const PropertyInfo &p_info, const Variant &p_default, bool p
|
|||
#define EDSCALE (EditorInterface::get_singleton()->get_editor_scale())
|
||||
|
||||
String TTR(const String &p_text, const String &p_context = "");
|
||||
#define RTR(m_text) TTR(m_text)
|
||||
|
||||
#endif // ! LIMBOAI_GDEXTENSION
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ LimboStringNames::LimboStringNames() {
|
|||
button_up = SN("button_up");
|
||||
call_deferred = SN("call_deferred");
|
||||
changed = SN("changed");
|
||||
Clear = SN("Clear");
|
||||
Close = SN("Close");
|
||||
dark_color_2 = SN("dark_color_2");
|
||||
Debug = SN("Debug");
|
||||
|
@ -67,6 +68,7 @@ LimboStringNames::LimboStringNames() {
|
|||
EVENT_FINISHED = SN("finished");
|
||||
EVENT_SUCCESS = SN("success");
|
||||
exited = SN("exited");
|
||||
ExternalLink = SN("ExternalLink");
|
||||
favorite_tasks_changed = SN("favorite_tasks_changed");
|
||||
Favorites = SN("Favorites");
|
||||
FlatButton = SN("FlatButton");
|
||||
|
@ -78,6 +80,7 @@ LimboStringNames::LimboStringNames() {
|
|||
freed = SN("freed");
|
||||
gui_input = SN("gui_input");
|
||||
GuiOptionArrow = SN("GuiOptionArrow");
|
||||
GuiTabMenuHl = SN("GuiTabMenuHl");
|
||||
GuiTreeArrowDown = SN("GuiTreeArrowDown");
|
||||
GuiTreeArrowRight = SN("GuiTreeArrowRight");
|
||||
HeaderSmall = SN("HeaderSmall");
|
||||
|
|
|
@ -63,6 +63,7 @@ public:
|
|||
StringName button_up;
|
||||
StringName call_deferred;
|
||||
StringName changed;
|
||||
StringName Clear;
|
||||
StringName Close;
|
||||
StringName dark_color_2;
|
||||
StringName Debug;
|
||||
|
@ -83,6 +84,7 @@ public:
|
|||
StringName EVENT_FINISHED;
|
||||
StringName EVENT_SUCCESS;
|
||||
StringName exited;
|
||||
StringName ExternalLink;
|
||||
StringName favorite_tasks_changed;
|
||||
StringName Favorites;
|
||||
StringName FlatButton;
|
||||
|
@ -94,6 +96,7 @@ public:
|
|||
StringName freed;
|
||||
StringName gui_input;
|
||||
StringName GuiOptionArrow;
|
||||
StringName GuiTabMenuHl;
|
||||
StringName GuiTreeArrowDown;
|
||||
StringName GuiTreeArrowRight;
|
||||
StringName HeaderSmall;
|
||||
|
|
Loading…
Reference in New Issue