diff --git a/blackboard/bb_param/bb_param.h b/blackboard/bb_param/bb_param.h index 4a74e2d..65e32cf 100644 --- a/blackboard/bb_param/bb_param.h +++ b/blackboard/bb_param/bb_param.h @@ -41,8 +41,6 @@ private: protected: static void _bind_methods(); - virtual Variant::Type get_type() const { return Variant::NIL; } - _FORCE_INLINE_ void _assign_default_value() { Callable::CallError err; Variant::construct(get_type(), saved_value, nullptr, 0, err); @@ -51,6 +49,8 @@ protected: void _get_property_list(List *p_list) const; public: + virtual Variant::Type get_type() const { return Variant::NIL; } + void set_value_source(ValueSource p_value); ValueSource get_value_source() const { return value_source; } diff --git a/blackboard/bb_param/bb_variant.h b/blackboard/bb_param/bb_variant.h index d8664bd..1eb95bc 100644 --- a/blackboard/bb_param/bb_variant.h +++ b/blackboard/bb_param/bb_variant.h @@ -25,10 +25,10 @@ private: protected: static void _bind_methods(); +public: virtual Variant::Type get_type() const override; void set_type(Variant::Type p_type); -public: BBVariant(); }; diff --git a/demo/ai/tasks/arrive_pos.gd b/demo/ai/tasks/arrive_pos.gd index 3c04885..e0e361d 100644 --- a/demo/ai/tasks/arrive_pos.gd +++ b/demo/ai/tasks/arrive_pos.gd @@ -11,7 +11,6 @@ @tool @icon("res://icon.png") -class_name ArrivePos extends BTAction @export var target_position_var := "target_position" diff --git a/demo/project.godot b/demo/project.godot index f100ae9..d2acb33 100644 --- a/demo/project.godot +++ b/demo/project.godot @@ -11,6 +11,7 @@ config_version=5 [application] config/name="LimboAI Test" +config/tags=PackedStringArray("demo") run/main_scene="res://examples/waypoints/example_waypoints.tscn" config/features=PackedStringArray("4.2") config/icon="res://icon.png" diff --git a/demo/tests/blackboard_parameters/test_bb_params.gd b/demo/tests/blackboard_parameters/test_bb_params.gd new file mode 100644 index 0000000..459fe5b --- /dev/null +++ b/demo/tests/blackboard_parameters/test_bb_params.gd @@ -0,0 +1,37 @@ +@tool +class_name TestBBParams +extends Resource + +@export var bool_param: BBBool +@export var int_param: BBInt +@export var float_param: BBFloat +@export var string_param: BBString +@export var vec2_param: BBVector2 +@export var vec2i_param: BBVector2i +@export var rect2_param: BBRect2 +@export var rect2i_param: BBRect2i +@export var vector3_param: BBVector3 +@export var vector3i_param: BBVector3i +@export var vector4_param: BBVector4 +@export var vector4i_param: BBVector4i +@export var transform2d_param: BBTransform2D +@export var plane_param: BBPlane +@export var quaternion_param: BBQuaternion +@export var aabb_param: BBAabb +@export var basis_param: BBBasis +@export var transform3d_param: BBTransform3D +#@export var projection_param: BBProjection +@export var color_param: BBColor +@export var stringname_param: BBStringName +@export var node_param: BBNode +@export var dictionary_param: BBDictionary +@export var array_param: BBArray +@export var byte_array_param: BBByteArray +@export var int_array_param: BBIntArray +@export var float_array_param: BBFloatArray +@export var string_array_param: BBStringArray +@export var vector2_array_param: BBVector2Array +@export var vector3_array_param: BBVector2Array +@export var color_array_param: BBColorArray +@export var variant_param: BBVariant + diff --git a/editor/editor_property_bb_param.cpp b/editor/editor_property_bb_param.cpp new file mode 100644 index 0000000..9d37ef6 --- /dev/null +++ b/editor/editor_property_bb_param.cpp @@ -0,0 +1,347 @@ +/** + * editor_property_bb_param.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 "editor_property_bb_param.h" + +#include "core/variant/variant.h" +#include "modules/limboai/blackboard/bb_param/bb_param.h" +#include "modules/limboai/blackboard/bb_param/bb_variant.h" +#include "modules/limboai/editor/mode_switch_button.h" + +#include "core/error/error_macros.h" +#include "core/io/marshalls.h" +#include "core/object/class_db.h" +#include "core/object/ref_counted.h" +#include "core/os/memory.h" +#include "core/string/print_string.h" +#include "editor/editor_inspector.h" +#include "editor/editor_properties.h" +#include "editor/editor_properties_array_dict.h" +#include "editor/editor_properties_vector.h" +#include "editor/editor_settings.h" +#include "scene/gui/base_button.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/menu_button.h" + +Ref EditorPropertyBBParam::_get_edited_param() { + Ref param = get_edited_property_value(); + if (param.is_null()) { + // Create parameter resource if null. + param = ClassDB::instantiate(param_type); + get_edited_object()->set(get_edited_property(), param); + } + return param; +} + +void EditorPropertyBBParam::_create_value_editor(Variant::Type p_type) { + if (value_editor) { + if (value_editor->get_meta(SNAME("_param_type")) == Variant(p_type)) { + return; + } + _remove_value_editor(); + } + + switch (p_type) { + case Variant::NIL: { + value_editor = memnew(EditorPropertyNil); + } break; + case Variant::BOOL: { + value_editor = memnew(EditorPropertyCheck); + } break; + case Variant::INT: { + EditorPropertyInteger *editor = memnew(EditorPropertyInteger); + editor->setup(-100000, 100000, 1, false, true, true); + value_editor = editor; + } break; + case Variant::FLOAT: { + EditorPropertyFloat *editor = memnew(EditorPropertyFloat); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true, false, true, true); + value_editor = editor; + } break; + case Variant::STRING: { + if (property_hint == PROPERTY_HINT_MULTILINE_TEXT) { + value_editor = memnew(EditorPropertyMultilineText); + } else { + value_editor = memnew(EditorPropertyText); + } + } break; + case Variant::VECTOR2: { + EditorPropertyVector2 *editor = memnew(EditorPropertyVector2); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::VECTOR2I: { + EditorPropertyVector2i *editor = memnew(EditorPropertyVector2i); + editor->setup(-100000, 100000); + value_editor = editor; + } break; + case Variant::RECT2: { + EditorPropertyRect2 *editor = memnew(EditorPropertyRect2); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::RECT2I: { + EditorPropertyRect2i *editor = memnew(EditorPropertyRect2i); + editor->setup(-100000, 100000); + value_editor = editor; + } break; + case Variant::VECTOR3: { + EditorPropertyVector3 *editor = memnew(EditorPropertyVector3); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::VECTOR3I: { + EditorPropertyVector3i *editor = memnew(EditorPropertyVector3i); + editor->setup(-100000, 100000); + value_editor = editor; + } break; + case Variant::VECTOR4: { + EditorPropertyVector4 *editor = memnew(EditorPropertyVector4); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::VECTOR4I: { + EditorPropertyVector4i *editor = memnew(EditorPropertyVector4i); + editor->setup(-100000, 100000); + value_editor = editor; + } break; + case Variant::TRANSFORM2D: { + EditorPropertyTransform2D *editor = memnew(EditorPropertyTransform2D); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::PLANE: { + EditorPropertyPlane *editor = memnew(EditorPropertyPlane); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::QUATERNION: { + EditorPropertyQuaternion *editor = memnew(EditorPropertyQuaternion); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::AABB: { + EditorPropertyAABB *editor = memnew(EditorPropertyAABB); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::BASIS: { + EditorPropertyBasis *editor = memnew(EditorPropertyBasis); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::TRANSFORM3D: { + EditorPropertyTransform3D *editor = memnew(EditorPropertyTransform3D); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::PROJECTION: { + EditorPropertyProjection *editor = memnew(EditorPropertyProjection); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::COLOR: { + value_editor = memnew(EditorPropertyColor); + } break; + case Variant::STRING_NAME: { + EditorPropertyText *editor = memnew(EditorPropertyText); + editor->set_string_name(true); + value_editor = editor; + } break; + case Variant::NODE_PATH: { + value_editor = memnew(EditorPropertyNodePath); + } break; + // case Variant::RID: { + // } break; + // case Variant::SIGNAL: { + // } break; + // case Variant::CALLABLE: { + // } break; + case Variant::OBJECT: { + // Only resources are supported. + EditorPropertyResource *editor = memnew(EditorPropertyResource); + editor->setup(_get_edited_param().ptr(), SNAME("saved_value"), "Resource"); + value_editor = editor; + } break; + case Variant::DICTIONARY: { + value_editor = memnew(EditorPropertyDictionary); + } break; + + case Variant::ARRAY: + case Variant::PACKED_BYTE_ARRAY: + case Variant::PACKED_INT32_ARRAY: + case Variant::PACKED_FLOAT32_ARRAY: + case Variant::PACKED_INT64_ARRAY: + case Variant::PACKED_FLOAT64_ARRAY: + case Variant::PACKED_STRING_ARRAY: + case Variant::PACKED_VECTOR2_ARRAY: + case Variant::PACKED_VECTOR3_ARRAY: + case Variant::PACKED_COLOR_ARRAY: { + EditorPropertyArray *editor = memnew(EditorPropertyArray); + editor->setup(p_type); + value_editor = editor; + } break; + + default: { + ERR_PRINT("Unexpected variant type!"); + value_editor = memnew(EditorPropertyNil); + } + } + value_editor->set_name_split_ratio(0.0); + editor_hbox->add_child(value_editor); + value_editor->set_h_size_flags(SIZE_EXPAND_FILL); + value_editor->set_meta(SNAME("_param_type"), p_type); + value_editor->connect(SNAME("property_changed"), callable_mp(this, &EditorPropertyBBParam::_value_edited)); +} + +void EditorPropertyBBParam::_remove_value_editor() { + if (value_editor) { + editor_hbox->remove_child(value_editor); + value_editor->queue_free(); + value_editor = nullptr; + } +} + +void EditorPropertyBBParam::_value_edited(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { + _get_edited_param()->set_saved_value(p_value); +} + +void EditorPropertyBBParam::_mode_changed() { + _get_edited_param()->set_value_source(mode_button->get_mode() == Mode::SPECIFY_VALUE ? BBParam::SAVED_VALUE : BBParam::BLACKBOARD_VAR); + update_property(); +} + +void EditorPropertyBBParam::_type_selected(int p_index) { + Ref param = _get_edited_param(); + ERR_FAIL_COND(param.is_null()); + param->set_type(Variant::Type(p_index)); + update_property(); +} + +void EditorPropertyBBParam::_variable_edited(const String &p_text) { + _get_edited_param()->set_variable(p_text); +} + +void EditorPropertyBBParam::update_property() { + Ref param = _get_edited_param(); + bool is_variant_param = param->is_class_ptr(BBVariant::get_class_ptr_static()); + + if (param->get_value_source() == BBParam::BLACKBOARD_VAR) { + _remove_value_editor(); + variable_edit->set_text(param->get_variable()); + variable_edit->set_editable(true); + variable_edit->show(); + mode_button->set_mode(Mode::BIND_VAR, true); + type_choice->hide(); + } else { + variable_edit->hide(); + _create_value_editor(param->get_type()); + value_editor->show(); + value_editor->set_object_and_property(param.ptr(), SNAME("saved_value")); + mode_button->set_mode(Mode::SPECIFY_VALUE, true); + value_editor->update_property(); + type_choice->set_visible(is_variant_param); + } + + if (is_variant_param) { + Variant::Type t = Variant::Type(param->get_type()); + String type_name = Variant::get_type_name(t); + type_choice->set_icon(get_editor_theme_icon(type_name)); + } +} + +void EditorPropertyBBParam::setup(PropertyHint p_hint, const String &p_hint_text) { + param_type = p_hint_text; + property_hint = p_hint; +} + +void EditorPropertyBBParam::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + { + String type = Variant::get_type_name(_get_edited_param()->get_type()); + type_choice->set_icon(get_editor_theme_icon(type)); + } + + // Initialize mode button. + mode_button->clear(); + mode_button->add_mode(Mode::SPECIFY_VALUE, get_editor_theme_icon(SNAME("LimboSpecifyValue")), TTR("Mode: Specify value.\nClick to switch mode.")); + mode_button->add_mode(Mode::BIND_VAR, get_editor_theme_icon(SNAME("BTSetVar")), TTR("Mode: Bind blackboard variable.\nClick to switch mode.")); + mode_button->set_mode(_get_edited_param()->get_value_source() == BBParam::BLACKBOARD_VAR ? Mode::BIND_VAR : Mode::SPECIFY_VALUE); + + bool is_variant_param = _get_edited_param()->is_class_ptr(BBVariant::get_class_ptr_static()); + if (is_variant_param) { + // Initialize type choice. + PopupMenu *type_menu = type_choice->get_popup(); + 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_editor_theme_icon(type), type, i); + } + } else { // Not a variant param. + type_choice->hide(); + } + } break; + } +} + +EditorPropertyBBParam::EditorPropertyBBParam() { + hbox = memnew(HBoxContainer); + add_child(hbox); + hbox->add_theme_constant_override(SNAME("separation"), 0); + + mode_button = memnew(ModeSwitchButton); + hbox->add_child(mode_button); + mode_button->set_focus_mode(FOCUS_NONE); + mode_button->connect(SNAME("mode_changed"), callable_mp(this, &EditorPropertyBBParam::_mode_changed)); + + type_choice = memnew(MenuButton); + hbox->add_child(type_choice); + type_choice->get_popup()->connect(SNAME("id_pressed"), callable_mp(this, &EditorPropertyBBParam::_type_selected)); + type_choice->set_tooltip_text(TTR("Click to choose type")); + type_choice->set_flat(false); + + editor_hbox = memnew(HBoxContainer); + hbox->add_child(editor_hbox); + editor_hbox->set_h_size_flags(SIZE_EXPAND_FILL); + editor_hbox->add_theme_constant_override(SNAME("separation"), 0); + + variable_edit = memnew(LineEdit); + editor_hbox->add_child(variable_edit); + variable_edit->set_placeholder(TTR("Variable")); + variable_edit->set_h_size_flags(SIZE_EXPAND_FILL); + variable_edit->connect(SNAME("text_changed"), callable_mp(this, &EditorPropertyBBParam::_variable_edited)); + + param_type = SNAME("BBString"); +} + +//***** EditorInspectorPluginBBParam + +bool EditorInspectorPluginBBParam::can_handle(Object *p_object) { + return true; // Handles everything. +} + +bool EditorInspectorPluginBBParam::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 p_usage, const bool p_wide) { + if (p_hint == PROPERTY_HINT_RESOURCE_TYPE && p_hint_text.begins_with("BB")) { + // TODO: Add more rigid hint check. + EditorPropertyBBParam *editor = memnew(EditorPropertyBBParam()); + editor->setup(p_hint, p_hint_text); + add_property_editor(p_path, editor); + return true; + } + return false; +} diff --git a/editor/editor_property_bb_param.h b/editor/editor_property_bb_param.h new file mode 100644 index 0000000..e1c2d40 --- /dev/null +++ b/editor/editor_property_bb_param.h @@ -0,0 +1,71 @@ +/** + * editor_property_bb_param.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. + * ============================================================================= + */ + +#ifndef EDITOR_PROPERTY_BB_PARAM_H +#define EDITOR_PROPERTY_BB_PARAM_H + +#include "editor/editor_inspector.h" + +#include "modules/limboai/blackboard/bb_param/bb_param.h" +#include "modules/limboai/editor/mode_switch_button.h" + +#include "scene/gui/box_container.h" +#include "scene/gui/menu_button.h" + +class EditorPropertyBBParam : public EditorProperty { + GDCLASS(EditorPropertyBBParam, EditorProperty); + +private: + enum Mode { + SPECIFY_VALUE, + BIND_VAR, + }; + + StringName param_type; + PropertyHint property_hint = PROPERTY_HINT_NONE; + Mode mode = Mode::SPECIFY_VALUE; + + HBoxContainer *hbox = nullptr; + HBoxContainer *editor_hbox = nullptr; + ModeSwitchButton *mode_button = nullptr; + EditorProperty *value_editor = nullptr; + LineEdit *variable_edit = nullptr; + MenuButton *type_choice = nullptr; + + Ref _get_edited_param(); + + void _create_value_editor(Variant::Type p_type); + void _remove_value_editor(); + + void _value_edited(const String &p_property, Variant p_value, const String &p_name = "", bool p_changing = false); + void _variable_edited(const String &p_text); + void _mode_changed(); + void _type_selected(int p_index); + +protected: + void _notification(int p_what); + +public: + virtual void update_property() override; + void setup(PropertyHint p_hint, const String &p_hint_text); + + EditorPropertyBBParam(); +}; + +class EditorInspectorPluginBBParam : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginBBParam, EditorInspectorPlugin); + +public: + 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 p_usage, const bool p_wide = false) override; +}; + +#endif // EDITOR_PROPERTY_BB_PARAM_H diff --git a/editor/limbo_ai_editor_plugin.cpp b/editor/limbo_ai_editor_plugin.cpp index 846fcd8..54516b4 100644 --- a/editor/limbo_ai_editor_plugin.cpp +++ b/editor/limbo_ai_editor_plugin.cpp @@ -18,6 +18,7 @@ #include "modules/limboai/bt/tasks/composites/bt_probability_selector.h" #include "modules/limboai/bt/tasks/composites/bt_selector.h" #include "modules/limboai/editor/debugger/limbo_debugger_plugin.h" +#include "modules/limboai/editor/editor_property_bb_param.h" #include "modules/limboai/util/limbo_utility.h" #include "core/config/project_settings.h" @@ -1157,6 +1158,7 @@ LimboAIEditorPlugin::LimboAIEditorPlugin() { EditorNode::get_singleton()->get_main_screen_control()->add_child(limbo_ai_editor); limbo_ai_editor->hide(); add_debugger_plugin(memnew(LimboDebuggerPlugin)); + add_inspector_plugin(memnew(EditorInspectorPluginBBParam)); } LimboAIEditorPlugin::~LimboAIEditorPlugin() { diff --git a/editor/mode_switch_button.cpp b/editor/mode_switch_button.cpp new file mode 100644 index 0000000..b50fa6e --- /dev/null +++ b/editor/mode_switch_button.cpp @@ -0,0 +1,73 @@ +/** + * mode_switch_button.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 "mode_switch_button.h" + +#include "core/error/error_macros.h" +#include "core/object/object.h" +#include "core/variant/variant.h" + +void ModeSwitchButton::add_mode(int p_id, const Ref &p_icon, const String &p_tooltip) { + bool unique_id = true; + for (int i = 0; i < modes.size(); i++) { + if (modes[i].id == p_id) { + unique_id = false; + break; + } + } + ERR_FAIL_COND_MSG(unique_id == false, "ID is already in use by another button state: " + itos(p_id)); + + Mode mode; + mode.id = p_id; + mode.icon = p_icon; + mode.tooltip = p_tooltip; + modes.append(mode); + + if (current_mode_index == -1) { + _set_mode_by_index(0); + } +} + +void ModeSwitchButton::set_mode(int p_id, bool p_no_signal) { + ERR_FAIL_COND_MSG(modes.is_empty(), "Cannot set button state with zero states."); + + int idx = -1; + for (int i = 0; i < modes.size(); i++) { + if (modes[i].id == p_id) { + idx = i; + break; + } + } + ERR_FAIL_COND_MSG(idx == -1, "Button state not found with such id: " + itos(p_id)); + + _set_mode_by_index(idx); + if (!p_no_signal) { + emit_signal(SNAME("mode_changed")); + } +} + +void ModeSwitchButton::clear() { + current_mode_index = -1; + modes.clear(); +} + +void ModeSwitchButton::_bind_methods() { + ClassDB::bind_method(D_METHOD("add_mode", "p_id", "p_icon", "p_tooltip"), &ModeSwitchButton::add_mode); + ClassDB::bind_method(D_METHOD("get_mode"), &ModeSwitchButton::get_mode); + ClassDB::bind_method(D_METHOD("set_mode", "p_id"), &ModeSwitchButton::set_mode); + ClassDB::bind_method(D_METHOD("next_mode"), &ModeSwitchButton::next_mode); + + ADD_SIGNAL(MethodInfo("mode_changed")); +} + +ModeSwitchButton::ModeSwitchButton() { + call_deferred(SNAME("connect"), SNAME("pressed"), callable_mp(this, &ModeSwitchButton::next_mode)); +} diff --git a/editor/mode_switch_button.h b/editor/mode_switch_button.h new file mode 100644 index 0000000..b27e92e --- /dev/null +++ b/editor/mode_switch_button.h @@ -0,0 +1,54 @@ +/** + * mode_switch_button.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. + * ============================================================================= + */ + +#ifndef MODE_SWITCH_BUTTON +#define MODE_SWITCH_BUTTON + +#include "scene/gui/button.h" + +#include "core/typedefs.h" +#include "scene/resources/texture.h" + +class ModeSwitchButton : public Button { + GDCLASS(ModeSwitchButton, Button); + +private: + struct Mode { + int id = 0; + Ref icon = nullptr; + String tooltip = ""; + }; + int current_mode_index = -1; + + Vector modes; + + _FORCE_INLINE_ void _set_mode_by_index(int p_index) { + current_mode_index = p_index; + set_icon(modes[current_mode_index].icon); + if (!modes[current_mode_index].tooltip.is_empty()) { + set_tooltip_text(modes[current_mode_index].tooltip); + } + } + +protected: + static void _bind_methods(); + +public: + void add_mode(int p_id, const Ref &p_icon, const String &p_tooltip = ""); + int get_mode() const { return modes.size() > 0 ? modes[current_mode_index].id : -1; } + void set_mode(int p_id, bool p_no_signal = false); + _FORCE_INLINE_ void next_mode() { set_mode((current_mode_index + 1) % modes.size()); }; + void clear(); + + ModeSwitchButton(); +}; + +#endif // MODE_SWITCH_BUTTON diff --git a/icons/LimboSpecifyValue.svg b/icons/LimboSpecifyValue.svg new file mode 100644 index 0000000..008bee7 --- /dev/null +++ b/icons/LimboSpecifyValue.svg @@ -0,0 +1 @@ + \ No newline at end of file