diff --git a/core/core_constants.cpp b/core/core_constants.cpp
index 4f3f1fd16e..3283d7fc44 100644
--- a/core/core_constants.cpp
+++ b/core/core_constants.cpp
@@ -593,6 +593,7 @@ void register_global_constants() {
BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_DEFERRED_SET_RESOURCE);
BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT);
BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_EDITOR_BASIC_SETTING);
+ BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_ARRAY);
BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_DEFAULT);
BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_DEFAULT_INTL);
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp
index e268a8d292..8e92340c1e 100644
--- a/core/object/class_db.cpp
+++ b/core/object/class_db.cpp
@@ -1028,6 +1028,18 @@ void ClassDB::add_property_subgroup(const StringName &p_class, const String &p_n
type->property_list.push_back(PropertyInfo(Variant::NIL, p_name, PROPERTY_HINT_NONE, p_prefix, PROPERTY_USAGE_SUBGROUP));
}
+void ClassDB::add_property_array_count(const StringName &p_class, const String &p_label, const StringName &p_count_property, const StringName &p_count_setter, const StringName &p_count_getter, const String &p_array_element_prefix, uint32_t p_count_usage) {
+ add_property(p_class, PropertyInfo(Variant::INT, p_count_property, PROPERTY_HINT_NONE, "", p_count_usage | PROPERTY_USAGE_ARRAY, vformat("%s,%s", p_label, p_array_element_prefix)), p_count_setter, p_count_getter);
+}
+
+void ClassDB::add_property_array(const StringName &p_class, const StringName &p_path, const String &p_array_element_prefix) {
+ OBJTYPE_WLOCK;
+ ClassInfo *type = classes.getptr(p_class);
+ ERR_FAIL_COND(!type);
+
+ type->property_list.push_back(PropertyInfo(Variant::NIL, p_path, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, p_array_element_prefix));
+}
+
// NOTE: For implementation simplicity reasons, this method doesn't allow setters to have optional arguments at the end.
void ClassDB::add_property(const StringName &p_class, const PropertyInfo &p_pinfo, const StringName &p_setter, const StringName &p_getter, int p_index) {
lock.read_lock();
diff --git a/core/object/class_db.h b/core/object/class_db.h
index 166aa35469..e89c7fffd7 100644
--- a/core/object/class_db.h
+++ b/core/object/class_db.h
@@ -353,6 +353,8 @@ public:
static void add_property_group(const StringName &p_class, const String &p_name, const String &p_prefix = "");
static void add_property_subgroup(const StringName &p_class, const String &p_name, const String &p_prefix = "");
+ static void add_property_array_count(const StringName &p_class, const String &p_label, const StringName &p_count_property, const StringName &p_count_setter, const StringName &p_count_getter, const String &p_array_element_prefix, uint32_t p_count_usage = PROPERTY_USAGE_EDITOR);
+ static void add_property_array(const StringName &p_class, const StringName &p_path, const String &p_array_element_prefix);
static void add_property(const StringName &p_class, const PropertyInfo &p_pinfo, const StringName &p_setter, const StringName &p_getter, int p_index = -1);
static void set_property_default_value(const StringName &p_class, const StringName &p_name, const Variant &p_default);
static void add_linked_property(const StringName &p_class, const String &p_property, const String &p_linked_property);
diff --git a/core/object/object.h b/core/object/object.h
index 1838e7eb3d..a44d921bff 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -132,7 +132,8 @@ enum PropertyUsageFlags {
PROPERTY_USAGE_DEFERRED_SET_RESOURCE = 1 << 26, // when loading, the resource for this property can be set at the end of loading
PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT = 1 << 27, // For Object properties, instantiate them when creating in editor.
PROPERTY_USAGE_EDITOR_BASIC_SETTING = 1 << 28, //for project or editor settings, show when basic settings are selected
- PROPERTY_USAGE_READ_ONLY = 1 << 29,
+ PROPERTY_USAGE_READ_ONLY = 1 << 29, // Mark a property as read-only in the inspector.
+ PROPERTY_USAGE_ARRAY = 1 << 30, // Used in the inspector to group properties as elements of an array.
PROPERTY_USAGE_DEFAULT = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_NETWORK,
PROPERTY_USAGE_DEFAULT_INTL = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_NETWORK | PROPERTY_USAGE_INTERNATIONALIZED,
@@ -147,6 +148,10 @@ enum PropertyUsageFlags {
#define ADD_SUBGROUP(m_name, m_prefix) ::ClassDB::add_property_subgroup(get_class_static(), m_name, m_prefix)
#define ADD_LINKED_PROPERTY(m_property, m_linked_property) ::ClassDB::add_linked_property(get_class_static(), m_property, m_linked_property)
+#define ADD_ARRAY_COUNT(m_label, m_count_property, m_count_property_setter, m_count_property_getter, m_prefix) ClassDB::add_property_array_count(get_class_static(), m_label, m_count_property, _scs_create(m_count_property_setter), _scs_create(m_count_property_getter), m_prefix)
+#define ADD_ARRAY_COUNT_WITH_USAGE_FLAGS(m_label, m_count_property, m_count_property_setter, m_count_property_getter, m_prefix, m_property_usage_flags) ClassDB::add_property_array_count(get_class_static(), m_label, m_count_property, _scs_create(m_count_property_setter), _scs_create(m_count_property_getter), m_prefix, m_property_usage_flags)
+#define ADD_ARRAY(m_array_path, m_prefix) ClassDB::add_property_array(get_class_static(), m_array_path, m_prefix)
+
struct PropertyInfo {
Variant::Type type = Variant::NIL;
String name;
diff --git a/core/variant/callable_bind.cpp b/core/variant/callable_bind.cpp
index 10446a5ec1..56eda6e703 100644
--- a/core/variant/callable_bind.cpp
+++ b/core/variant/callable_bind.cpp
@@ -169,7 +169,8 @@ CallableCustomUnbind::~CallableCustomUnbind() {
}
Callable callable_bind(const Callable &p_callable, const Variant &p_arg1) {
- return p_callable.bind((const Variant **)&p_arg1, 1);
+ const Variant *args[1] = { &p_arg1 };
+ return p_callable.bind(args, 1);
}
Callable callable_bind(const Callable &p_callable, const Variant &p_arg1, const Variant &p_arg2) {
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index 616d88c7f3..a18da1cf16 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -2425,6 +2425,8 @@
+
+
Default usage (storage, editor and network).
diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml
index f3c64c3c7d..e92a7331e6 100644
--- a/doc/classes/TileMap.xml
+++ b/doc/classes/TileMap.xml
@@ -17,6 +17,12 @@
https://godotengine.org/asset-library/asset/113
+
+
+
+
+
+
@@ -71,6 +77,11 @@
+
+
+
+
+
@@ -116,6 +127,19 @@
Returns the local position corresponding to the given tilemap (grid-based) coordinates.
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -176,8 +200,6 @@
-
-
diff --git a/doc/classes/TileSet.xml b/doc/classes/TileSet.xml
index 439c6e3830..3e0ab87383 100644
--- a/doc/classes/TileSet.xml
+++ b/doc/classes/TileSet.xml
@@ -17,6 +17,30 @@
https://godotengine.org/asset-library/asset/113
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -24,6 +48,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -49,12 +86,22 @@
+
+
+
+
+
+
+
+
+
+
@@ -72,6 +119,11 @@
+
+
+
+
+
@@ -90,6 +142,11 @@
+
+
+
+
+
@@ -133,6 +190,11 @@
+
+
+
+
+
@@ -174,6 +236,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -189,6 +294,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -201,6 +330,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -300,25 +442,8 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp
index fee2deddda..d04875f188 100644
--- a/editor/doc_tools.cpp
+++ b/editor/doc_tools.cpp
@@ -277,7 +277,7 @@ void DocTools::generate(bool p_basic_types) {
EO = EO->next();
}
- if (E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP || E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_INTERNAL) {
+ if (E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP || E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_INTERNAL || (E.type == Variant::NIL && E.usage & PROPERTY_USAGE_ARRAY)) {
continue;
}
diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp
index c62e5b75b2..e40bbefef8 100644
--- a/editor/editor_data.cpp
+++ b/editor/editor_data.cpp
@@ -438,6 +438,21 @@ const Vector EditorData::get_undo_redo_inspector_hook_callback() {
return undo_redo_callbacks;
}
+void EditorData::add_move_array_element_function(const StringName &p_class, Callable p_callable) {
+ move_element_functions.insert(p_class, p_callable);
+}
+
+void EditorData::remove_move_array_element_function(const StringName &p_class) {
+ move_element_functions.erase(p_class);
+}
+
+Callable EditorData::get_move_array_element_function(const StringName &p_class) const {
+ if (move_element_functions.has(p_class)) {
+ return move_element_functions[p_class];
+ }
+ return Callable();
+}
+
void EditorData::remove_editor_plugin(EditorPlugin *p_plugin) {
p_plugin->undo_redo = nullptr;
editor_plugins.erase(p_plugin);
diff --git a/editor/editor_data.h b/editor/editor_data.h
index df6ba9d0c9..9184ddcf39 100644
--- a/editor/editor_data.h
+++ b/editor/editor_data.h
@@ -133,6 +133,7 @@ private:
List clipboard;
UndoRedo undo_redo;
Vector undo_redo_callbacks;
+ Map move_element_functions;
void _cleanup_history();
@@ -167,10 +168,14 @@ public:
EditorPlugin *get_editor_plugin(int p_idx);
UndoRedo &get_undo_redo();
- void add_undo_redo_inspector_hook_callback(Callable p_callable); // Callbacks should have 4 args: (Object* undo_redo, Object *modified_object, String property, Variant new_value)
+ void add_undo_redo_inspector_hook_callback(Callable p_callable); // Callbacks should have this signature: void (Object* undo_redo, Object *modified_object, String property, Variant new_value)
void remove_undo_redo_inspector_hook_callback(Callable p_callable);
const Vector get_undo_redo_inspector_hook_callback();
+ void add_move_array_element_function(const StringName &p_class, Callable p_callable); // Function should have this signature: void (Object* undo_redo, Object *modified_object, String array_prefix, int element_index, int new_position)
+ void remove_move_array_element_function(const StringName &p_class);
+ Callable get_move_array_element_function(const StringName &p_class) const;
+
void save_editor_global_states();
void restore_editor_global_states();
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index 03d1521bab..66f291bd3e 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -1184,147 +1184,144 @@ EditorInspectorCategory::EditorInspectorCategory() {
void EditorInspectorSection::_test_unfold() {
if (!vbox_added) {
add_child(vbox);
+ move_child(vbox, 0);
vbox_added = true;
}
}
void EditorInspectorSection::_notification(int p_what) {
- if (p_what == NOTIFICATION_SORT_CHILDREN) {
- Ref font = get_theme_font(SNAME("bold"), SNAME("EditorFonts"));
- int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts"));
+ switch (p_what) {
+ case NOTIFICATION_THEME_CHANGED: {
+ minimum_size_changed();
+ } break;
+ case NOTIFICATION_SORT_CHILDREN: {
+ if (!vbox_added) {
+ return;
+ }
+ // Get the section header font.
+ Ref font = get_theme_font(SNAME("bold"), SNAME("EditorFonts"));
+ int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts"));
- Ref arrow;
-
- if (foldable) {
- if (object->editor_is_section_unfolded(section)) {
- arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree"));
- } else {
- if (is_layout_rtl()) {
- arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree"));
+ // Get the right direction arrow texture, if the section is foldable.
+ Ref arrow;
+ if (foldable) {
+ if (object->editor_is_section_unfolded(section)) {
+ arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree"));
} else {
- arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree"));
+ if (is_layout_rtl()) {
+ arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree"));
+ } else {
+ arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree"));
+ }
}
}
- }
- Size2 size = get_size();
- Point2 offset;
- Rect2 rect;
- offset.y = font->get_height(font_size);
- if (arrow.is_valid()) {
- offset.y = MAX(offset.y, arrow->get_height());
- }
-
- offset.y += get_theme_constant(SNAME("vseparation"), SNAME("Tree"));
- if (is_layout_rtl()) {
- rect = Rect2(offset, size - offset - Vector2(get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")), 0));
- } else {
- offset.x += get_theme_constant(SNAME("inspector_margin"), SNAME("Editor"));
- rect = Rect2(offset, size - offset);
- }
-
- //set children
- for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to(get_child(i));
- if (!c) {
- continue;
+ // Compute the height of the section header.
+ int header_height = font->get_height(font_size);
+ if (arrow.is_valid()) {
+ header_height = MAX(header_height, arrow->get_height());
}
- if (c->is_set_as_top_level()) {
- continue;
- }
- if (!c->is_visible_in_tree()) {
- continue;
+ header_height += get_theme_constant(SNAME("vseparation"), SNAME("Tree"));
+
+ int inspector_margin = get_theme_constant(SNAME("inspector_margin"), SNAME("Editor"));
+ Size2 size = get_size() - Vector2(inspector_margin, 0);
+ Vector2 offset = Vector2(is_layout_rtl() ? 0 : inspector_margin, header_height);
+ for (int i = 0; i < get_child_count(); i++) {
+ Control *c = Object::cast_to(get_child(i));
+ if (!c) {
+ continue;
+ }
+ if (c->is_set_as_top_level()) {
+ continue;
+ }
+
+ fit_child_in_rect(c, Rect2(offset, size));
}
+ } break;
+ case NOTIFICATION_DRAW: {
+ // Get the section header font.
+ Ref font = get_theme_font(SNAME("bold"), SNAME("EditorFonts"));
+ int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts"));
- fit_child_in_rect(c, rect);
- }
-
- update(); //need to redraw text
- }
-
- if (p_what == NOTIFICATION_DRAW) {
- Ref arrow;
- bool rtl = is_layout_rtl();
-
- if (foldable) {
- if (object->editor_is_section_unfolded(section)) {
- arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree"));
- } else {
- if (is_layout_rtl()) {
- arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree"));
+ // Get the right direction arrow texture, if the section is foldable.
+ Ref arrow;
+ if (foldable) {
+ if (object->editor_is_section_unfolded(section)) {
+ arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree"));
} else {
- arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree"));
+ if (is_layout_rtl()) {
+ arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree"));
+ } else {
+ arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree"));
+ }
}
}
- }
- Ref font = get_theme_font(SNAME("bold"), SNAME("EditorFonts"));
- int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts"));
+ bool rtl = is_layout_rtl();
- int h = font->get_height(font_size);
- if (arrow.is_valid()) {
- h = MAX(h, arrow->get_height());
- }
- h += get_theme_constant(SNAME("vseparation"), SNAME("Tree"));
-
- Color c = bg_color;
- c.a *= 0.4;
- draw_rect(Rect2(Vector2(), Vector2(get_size().width, h)), c);
-
- const int arrow_margin = 2;
- const int arrow_width = arrow.is_valid() ? arrow->get_width() : 0;
- Color color = get_theme_color(SNAME("font_color"));
- float text_width = get_size().width - Math::round(arrow_width + arrow_margin * EDSCALE);
- draw_string(font, Point2(rtl ? 0 : Math::round(arrow_width + arrow_margin * EDSCALE), font->get_ascent(font_size) + (h - font->get_height(font_size)) / 2).floor(), label, rtl ? HALIGN_RIGHT : HALIGN_LEFT, text_width, font_size, color);
-
- if (arrow.is_valid()) {
- if (rtl) {
- draw_texture(arrow, Point2(get_size().width - arrow->get_width() - Math::round(arrow_margin * EDSCALE), (h - arrow->get_height()) / 2).floor());
- } else {
- draw_texture(arrow, Point2(Math::round(arrow_margin * EDSCALE), (h - arrow->get_height()) / 2).floor());
+ // Compute the height of the section header.
+ int header_height = font->get_height(font_size);
+ if (arrow.is_valid()) {
+ header_height = MAX(header_height, arrow->get_height());
}
- }
+ header_height += get_theme_constant(SNAME("vseparation"), SNAME("Tree"));
- if (dropping && !vbox->is_visible_in_tree()) {
- Color accent_color = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
- draw_rect(Rect2(Point2(), get_size()), accent_color, false);
- }
- }
+ Color c = bg_color;
+ c.a *= 0.4;
+ draw_rect(Rect2(Vector2(), Vector2(get_size().width, header_height)), c);
- if (p_what == NOTIFICATION_DRAG_BEGIN) {
- Dictionary dd = get_viewport()->gui_get_drag_data();
+ const int arrow_margin = 2;
+ const int arrow_width = arrow.is_valid() ? arrow->get_width() : 0;
+ Color color = get_theme_color(SNAME("font_color"));
+ float text_width = get_size().width - Math::round(arrow_width + arrow_margin * EDSCALE);
+ draw_string(font, Point2(rtl ? 0 : Math::round(arrow_width + arrow_margin * EDSCALE), font->get_ascent(font_size) + (header_height - font->get_height(font_size)) / 2).floor(), label, rtl ? HALIGN_RIGHT : HALIGN_LEFT, text_width, font_size, color);
- // Only allow dropping if the section contains properties which can take the dragged data.
- bool children_can_drop = false;
- for (int child_idx = 0; child_idx < vbox->get_child_count(); child_idx++) {
- Control *editor_property = Object::cast_to(vbox->get_child(child_idx));
-
- // Test can_drop_data and can_drop_data_fw, since can_drop_data only works if set up with forwarding or if script attached.
- if (editor_property && (editor_property->can_drop_data(Point2(), dd) || editor_property->call("_can_drop_data_fw", Point2(), dd, this))) {
- children_can_drop = true;
- break;
+ if (arrow.is_valid()) {
+ if (rtl) {
+ draw_texture(arrow, Point2(get_size().width - arrow->get_width() - Math::round(arrow_margin * EDSCALE), (header_height - arrow->get_height()) / 2).floor());
+ } else {
+ draw_texture(arrow, Point2(Math::round(arrow_margin * EDSCALE), (header_height - arrow->get_height()) / 2).floor());
+ }
}
- }
- dropping = children_can_drop;
- update();
- }
+ if (dropping && !vbox->is_visible_in_tree()) {
+ Color accent_color = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
+ draw_rect(Rect2(Point2(), get_size()), accent_color, false);
+ }
+ } break;
+ case NOTIFICATION_DRAG_BEGIN: {
+ Dictionary dd = get_viewport()->gui_get_drag_data();
- if (p_what == NOTIFICATION_DRAG_END) {
- dropping = false;
- update();
- }
+ // Only allow dropping if the section contains properties which can take the dragged data.
+ bool children_can_drop = false;
+ for (int child_idx = 0; child_idx < vbox->get_child_count(); child_idx++) {
+ Control *editor_property = Object::cast_to(vbox->get_child(child_idx));
- if (p_what == NOTIFICATION_MOUSE_ENTER) {
- if (dropping) {
- dropping_unfold_timer->start();
- }
- }
+ // Test can_drop_data and can_drop_data_fw, since can_drop_data only works if set up with forwarding or if script attached.
+ if (editor_property && (editor_property->can_drop_data(Point2(), dd) || editor_property->call("_can_drop_data_fw", Point2(), dd, this))) {
+ children_can_drop = true;
+ break;
+ }
+ }
- if (p_what == NOTIFICATION_MOUSE_EXIT) {
- if (dropping) {
- dropping_unfold_timer->stop();
- }
+ dropping = children_can_drop;
+ update();
+ } break;
+ case NOTIFICATION_DRAG_END: {
+ dropping = false;
+ update();
+ } break;
+ case NOTIFICATION_MOUSE_ENTER: {
+ if (dropping) {
+ dropping_unfold_timer->start();
+ }
+ } break;
+
+ case NOTIFICATION_MOUSE_EXIT: {
+ if (dropping) {
+ dropping_unfold_timer->stop();
+ }
+ } break;
}
}
@@ -1363,6 +1360,7 @@ void EditorInspectorSection::setup(const String &p_section, const String &p_labe
if (!foldable && !vbox_added) {
add_child(vbox);
+ move_child(vbox, 0);
vbox_added = true;
}
@@ -1422,7 +1420,7 @@ void EditorInspectorSection::fold() {
}
if (!vbox_added) {
- return; //kinda pointless
+ return;
}
object->editor_set_section_unfold(section, false);
@@ -1457,6 +1455,732 @@ EditorInspectorSection::~EditorInspectorSection() {
}
}
+////////////////////////////////////////////////
+////////////////////////////////////////////////
+int EditorInspectorArray::_get_array_count() {
+ if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
+ List object_property_list;
+ object->get_property_list(&object_property_list);
+ return _extract_properties_as_array(object_property_list).size();
+ } else if (mode == MODE_USE_COUNT_PROPERTY) {
+ bool valid;
+ int count = object->get(count_property, &valid);
+ ERR_FAIL_COND_V_MSG(!valid, 0, vformat("%s is not a valid property to be used as array count.", count_property));
+ return count;
+ }
+ return 0;
+}
+
+void EditorInspectorArray::_add_button_pressed() {
+ _move_element(-1, -1);
+}
+
+void EditorInspectorArray::_first_page_button_pressed() {
+ emit_signal("page_change_request", 0);
+}
+
+void EditorInspectorArray::_prev_page_button_pressed() {
+ emit_signal("page_change_request", MAX(0, page - 1));
+}
+
+void EditorInspectorArray::_page_line_edit_text_submitted(String p_text) {
+ if (p_text.is_valid_int()) {
+ int new_page = p_text.to_int() - 1;
+ new_page = MIN(MAX(0, new_page), max_page);
+ page_line_edit->set_text(Variant(new_page));
+ emit_signal("page_change_request", new_page);
+ } else {
+ page_line_edit->set_text(Variant(page));
+ }
+}
+
+void EditorInspectorArray::_next_page_button_pressed() {
+ emit_signal("page_change_request", MIN(max_page, page + 1));
+}
+
+void EditorInspectorArray::_last_page_button_pressed() {
+ emit_signal("page_change_request", max_page);
+}
+
+void EditorInspectorArray::_rmb_popup_id_pressed(int p_id) {
+ switch (p_id) {
+ case OPTION_MOVE_UP:
+ if (popup_array_index_pressed > 0) {
+ _move_element(popup_array_index_pressed, popup_array_index_pressed - 1);
+ }
+ break;
+ case OPTION_MOVE_DOWN:
+ if (popup_array_index_pressed < count - 1) {
+ _move_element(popup_array_index_pressed, popup_array_index_pressed + 2);
+ }
+ break;
+ case OPTION_NEW_BEFORE:
+ _move_element(-1, popup_array_index_pressed);
+ break;
+ case OPTION_NEW_AFTER:
+ _move_element(-1, popup_array_index_pressed + 1);
+ break;
+ case OPTION_REMOVE:
+ _move_element(popup_array_index_pressed, -1);
+ break;
+ case OPTION_CLEAR_ARRAY:
+ _clear_array();
+ break;
+ case OPTION_RESIZE_ARRAY:
+ new_size = count;
+ new_size_line_edit->set_text(Variant(new_size));
+ resize_dialog->get_ok_button()->set_disabled(true);
+ resize_dialog->popup_centered();
+ new_size_line_edit->grab_focus();
+ new_size_line_edit->select_all();
+ break;
+ default:
+ break;
+ }
+}
+
+void EditorInspectorArray::_control_dropping_draw() {
+ int drop_position = _drop_position();
+
+ if (dropping && drop_position >= 0) {
+ Vector2 from;
+ Vector2 to;
+ if (drop_position < elements_vbox->get_child_count()) {
+ Transform2D xform = Object::cast_to(elements_vbox->get_child(drop_position))->get_transform();
+ from = xform.xform(Vector2());
+ to = xform.xform(Vector2(elements_vbox->get_size().x, 0));
+ } else {
+ Control *child = Object::cast_to(elements_vbox->get_child(drop_position - 1));
+ Transform2D xform = child->get_transform();
+ from = xform.xform(Vector2(0, child->get_size().y));
+ to = xform.xform(Vector2(elements_vbox->get_size().x, child->get_size().y));
+ }
+ Color color = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
+ control_dropping->draw_line(from, to, color, 2);
+ }
+}
+
+void EditorInspectorArray::_vbox_visibility_changed() {
+ control_dropping->set_visible(vbox->is_visible_in_tree());
+}
+
+void EditorInspectorArray::_panel_draw(int p_index) {
+ ERR_FAIL_INDEX(p_index, (int)array_elements.size());
+
+ Ref style = get_theme_stylebox("Focus", "EditorStyles");
+ if (!style.is_valid()) {
+ return;
+ }
+ if (array_elements[p_index].panel->has_focus()) {
+ array_elements[p_index].panel->draw_style_box(style, Rect2(Vector2(), array_elements[p_index].panel->get_size()));
+ }
+}
+
+void EditorInspectorArray::_panel_gui_input(Ref p_event, int p_index) {
+ ERR_FAIL_INDEX(p_index, (int)array_elements.size());
+
+ Ref key_ref = p_event;
+ if (key_ref.is_valid()) {
+ const InputEventKey &key = **key_ref;
+
+ if (array_elements[p_index].panel->has_focus() && key.is_pressed() && key.get_keycode() == KEY_DELETE) {
+ _move_element(begin_array_index + p_index, -1);
+ array_elements[p_index].panel->accept_event();
+ }
+ }
+
+ Ref mb = p_event;
+ if (mb.is_valid()) {
+ if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
+ popup_array_index_pressed = begin_array_index + p_index;
+ rmb_popup->set_item_disabled(OPTION_MOVE_UP, popup_array_index_pressed == 0);
+ rmb_popup->set_item_disabled(OPTION_MOVE_DOWN, popup_array_index_pressed == count - 1);
+ rmb_popup->set_position(mb->get_global_position());
+ rmb_popup->set_size(Vector2());
+ rmb_popup->popup();
+ }
+ }
+}
+
+void EditorInspectorArray::_move_element(int p_element_index, int p_to_pos) {
+ String action_name;
+ if (p_element_index < 0) {
+ action_name = vformat("Add element to property array with prefix %s.", array_element_prefix);
+ } else if (p_to_pos < 0) {
+ action_name = vformat("Remove element %d from property array with prefix %s.", p_element_index, array_element_prefix);
+ } else {
+ action_name = vformat("Move element %d to position %d in property array with prefix %s.", p_element_index, p_to_pos, array_element_prefix);
+ }
+ undo_redo->create_action(action_name);
+ if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
+ // Call the function.
+ Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name());
+ if (move_function.is_valid()) {
+ Variant args[] = { (Object *)undo_redo, object, array_element_prefix, p_element_index, p_to_pos };
+ const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] };
+ Variant return_value;
+ Callable::CallError call_error;
+ move_function.call(args_p, 5, return_value, call_error);
+ } else {
+ WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name()));
+ }
+ } else if (mode == MODE_USE_COUNT_PROPERTY) {
+ ERR_FAIL_COND(p_to_pos < -1 || p_to_pos > count);
+ List object_property_list;
+ object->get_property_list(&object_property_list);
+
+ Array properties_as_array = _extract_properties_as_array(object_property_list);
+ properties_as_array.resize(count);
+
+ // For undoing things
+ undo_redo->add_undo_property(object, count_property, properties_as_array.size());
+ for (int i = 0; i < (int)properties_as_array.size(); i++) {
+ Dictionary d = Dictionary(properties_as_array[i]);
+ Array keys = d.keys();
+ for (int j = 0; j < keys.size(); j++) {
+ String key = keys[j];
+ undo_redo->add_undo_property(object, vformat(key, i), d[key]);
+ }
+ }
+
+ if (p_element_index < 0) {
+ // Add an element.
+ properties_as_array.insert(p_to_pos < 0 ? properties_as_array.size() : p_to_pos, Dictionary());
+ } else if (p_to_pos < 0) {
+ // Delete the element.
+ properties_as_array.remove(p_element_index);
+ } else {
+ // Move the element.
+ properties_as_array.insert(p_to_pos, properties_as_array[p_element_index].duplicate());
+ properties_as_array.remove(p_to_pos < p_element_index ? p_element_index + 1 : p_element_index);
+ }
+
+ // Change the array size then set the properties.
+ undo_redo->add_do_property(object, count_property, properties_as_array.size());
+ for (int i = 0; i < (int)properties_as_array.size(); i++) {
+ Dictionary d = properties_as_array[i];
+ Array keys = d.keys();
+ for (int j = 0; j < keys.size(); j++) {
+ String key = keys[j];
+ undo_redo->add_do_property(object, vformat(key, i), d[key]);
+ }
+ }
+ }
+ undo_redo->commit_action();
+
+ // Handle page change and update counts.
+ if (p_element_index < 0) {
+ int added_index = p_to_pos < 0 ? count : p_to_pos;
+ emit_signal("page_change_request", added_index / page_lenght);
+ count += 1;
+ } else if (p_to_pos < 0) {
+ count -= 1;
+ if (page == max_page && (MAX(0, count - 1) / page_lenght != max_page)) {
+ emit_signal("page_change_request", max_page - 1);
+ }
+ }
+ begin_array_index = page * page_lenght;
+ end_array_index = MIN(count, (page + 1) * page_lenght);
+ max_page = MAX(0, count - 1) / page_lenght;
+}
+
+void EditorInspectorArray::_clear_array() {
+ undo_redo->create_action(vformat("Clear property array with prefix %s.", array_element_prefix));
+ if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
+ for (int i = count - 1; i >= 0; i--) {
+ // Call the function.
+ Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name());
+ if (move_function.is_valid()) {
+ Variant args[] = { (Object *)undo_redo, object, array_element_prefix, i, -1 };
+ const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] };
+ Variant return_value;
+ Callable::CallError call_error;
+ move_function.call(args_p, 5, return_value, call_error);
+ } else {
+ WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name()));
+ }
+ }
+ } else if (mode == MODE_USE_COUNT_PROPERTY) {
+ List object_property_list;
+ object->get_property_list(&object_property_list);
+
+ Array properties_as_array = _extract_properties_as_array(object_property_list);
+ properties_as_array.resize(count);
+
+ // For undoing things
+ undo_redo->add_undo_property(object, count_property, count);
+ for (int i = 0; i < (int)properties_as_array.size(); i++) {
+ Dictionary d = Dictionary(properties_as_array[i]);
+ Array keys = d.keys();
+ for (int j = 0; j < keys.size(); j++) {
+ String key = keys[j];
+ undo_redo->add_undo_property(object, vformat(key, i), d[key]);
+ }
+ }
+
+ // Change the array size then set the properties.
+ undo_redo->add_do_property(object, count_property, 0);
+ }
+ undo_redo->commit_action();
+
+ // Handle page change and update counts.
+ emit_signal("page_change_request", 0);
+ count = 0;
+ begin_array_index = 0;
+ end_array_index = 0;
+ max_page = 0;
+}
+
+void EditorInspectorArray::_resize_array(int p_size) {
+ ERR_FAIL_COND(p_size < 0);
+ if (p_size == count) {
+ return;
+ }
+
+ undo_redo->create_action(vformat("Resize property array with prefix %s.", array_element_prefix));
+ if (p_size > count) {
+ if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
+ for (int i = count; i < p_size; i++) {
+ // Call the function.
+ Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name());
+ if (move_function.is_valid()) {
+ Variant args[] = { (Object *)undo_redo, object, array_element_prefix, -1, -1 };
+ const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] };
+ Variant return_value;
+ Callable::CallError call_error;
+ move_function.call(args_p, 5, return_value, call_error);
+ } else {
+ WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name()));
+ }
+ }
+ } else if (mode == MODE_USE_COUNT_PROPERTY) {
+ undo_redo->add_undo_property(object, count_property, count);
+ undo_redo->add_do_property(object, count_property, p_size);
+ }
+ } else {
+ if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
+ for (int i = count - 1; i > p_size - 1; i--) {
+ // Call the function.
+ Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name());
+ if (move_function.is_valid()) {
+ Variant args[] = { (Object *)undo_redo, object, array_element_prefix, i, -1 };
+ const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] };
+ Variant return_value;
+ Callable::CallError call_error;
+ move_function.call(args_p, 5, return_value, call_error);
+ } else {
+ WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name()));
+ }
+ }
+ } else if (mode == MODE_USE_COUNT_PROPERTY) {
+ List object_property_list;
+ object->get_property_list(&object_property_list);
+
+ Array properties_as_array = _extract_properties_as_array(object_property_list);
+ properties_as_array.resize(count);
+
+ // For undoing things
+ undo_redo->add_undo_property(object, count_property, count);
+ for (int i = count - 1; i > p_size - 1; i--) {
+ Dictionary d = Dictionary(properties_as_array[i]);
+ Array keys = d.keys();
+ for (int j = 0; j < keys.size(); j++) {
+ String key = keys[j];
+ undo_redo->add_undo_property(object, vformat(key, i), d[key]);
+ }
+ }
+
+ // Change the array size then set the properties.
+ undo_redo->add_do_property(object, count_property, p_size);
+ }
+ }
+ undo_redo->commit_action();
+
+ // Handle page change and update counts.
+ emit_signal("page_change_request", 0);
+ /*
+ count = 0;
+ begin_array_index = 0;
+ end_array_index = 0;
+ max_page = 0;
+ */
+}
+
+Array EditorInspectorArray::_extract_properties_as_array(const List &p_list) {
+ Array output;
+
+ for (const PropertyInfo &pi : p_list) {
+ if (pi.name.begins_with(array_element_prefix)) {
+ String str = pi.name.trim_prefix(array_element_prefix);
+
+ int to_char_index = 0;
+ while (to_char_index < str.length()) {
+ if (str[to_char_index] < '0' || str[to_char_index] > '9') {
+ break;
+ }
+ to_char_index++;
+ }
+ if (to_char_index > 0) {
+ int array_index = str.left(to_char_index).to_int();
+ Error error = OK;
+ if (array_index >= output.size()) {
+ error = output.resize(array_index + 1);
+ }
+ if (error == OK) {
+ String format_string = String(array_element_prefix) + "%d" + str.substr(to_char_index);
+ Dictionary dict = output[array_index];
+ dict[format_string] = object->get(pi.name);
+ output[array_index] = dict;
+ } else {
+ WARN_PRINT(vformat("Array element %s has an index too high. Array allocaiton failed.", pi.name));
+ }
+ }
+ }
+ }
+ return output;
+}
+
+int EditorInspectorArray::_drop_position() const {
+ for (int i = 0; i < (int)array_elements.size(); i++) {
+ const ArrayElement &ae = array_elements[i];
+
+ Size2 size = ae.panel->get_size();
+ Vector2 mp = ae.panel->get_local_mouse_position();
+
+ if (Rect2(Vector2(), size).has_point(mp)) {
+ if (mp.y < size.y / 2) {
+ return i;
+ } else {
+ return i + 1;
+ }
+ }
+ }
+ return -1;
+}
+
+void EditorInspectorArray::_new_size_line_edit_text_changed(String p_text) {
+ bool valid = false;
+ ;
+ if (p_text.is_valid_int()) {
+ int val = p_text.to_int();
+ if (val > 0 && val != count) {
+ valid = true;
+ }
+ }
+ resize_dialog->get_ok_button()->set_disabled(!valid);
+}
+
+void EditorInspectorArray::_new_size_line_edit_text_submitted(String p_text) {
+ bool valid = false;
+ ;
+ if (p_text.is_valid_int()) {
+ int val = p_text.to_int();
+ if (val > 0 && val != count) {
+ new_size = val;
+ valid = true;
+ }
+ }
+ if (valid) {
+ resize_dialog->hide();
+ _resize_array(new_size);
+ } else {
+ new_size_line_edit->set_text(Variant(new_size));
+ }
+}
+
+void EditorInspectorArray::_resize_dialog_confirmed() {
+ _new_size_line_edit_text_submitted(new_size_line_edit->get_text());
+}
+
+void EditorInspectorArray::_setup() {
+ // Setup counts.
+ count = _get_array_count();
+ begin_array_index = page * page_lenght;
+ end_array_index = MIN(count, (page + 1) * page_lenght);
+ max_page = MAX(0, count - 1) / page_lenght;
+ array_elements.resize(MAX(0, end_array_index - begin_array_index));
+ if (page < 0 || page > max_page) {
+ WARN_PRINT(vformat("Invalid page number %d", page));
+ page = CLAMP(page, 0, max_page);
+ }
+
+ for (int i = 0; i < (int)array_elements.size(); i++) {
+ ArrayElement &ae = array_elements[i];
+
+ // Panel and its hbox.
+ ae.panel = memnew(PanelContainer);
+ ae.panel->set_focus_mode(FOCUS_ALL);
+ ae.panel->set_mouse_filter(MOUSE_FILTER_PASS);
+ ae.panel->set_drag_forwarding(this);
+ ae.panel->set_meta("index", begin_array_index + i);
+ ae.panel->set_tooltip(vformat(TTR("Element %d: %s%d*"), i, array_element_prefix, i));
+ ae.panel->connect("focus_entered", callable_mp((CanvasItem *)ae.panel, &PanelContainer::update));
+ ae.panel->connect("focus_exited", callable_mp((CanvasItem *)ae.panel, &PanelContainer::update));
+ ae.panel->connect("draw", callable_bind(callable_mp(this, &EditorInspectorArray::_panel_draw), i));
+ ae.panel->connect("gui_input", callable_bind(callable_mp(this, &EditorInspectorArray::_panel_gui_input), i));
+ ae.panel->add_theme_style_override(SNAME("panel"), i % 2 ? odd_style : even_style);
+ elements_vbox->add_child(ae.panel);
+
+ ae.margin = memnew(MarginContainer);
+ ae.margin->set_mouse_filter(MOUSE_FILTER_PASS);
+ if (is_inside_tree()) {
+ Size2 min_size = get_theme_stylebox("Focus", "EditorStyles")->get_minimum_size();
+ ae.margin->add_theme_constant_override("margin_left", min_size.x / 2);
+ ae.margin->add_theme_constant_override("margin_top", min_size.y / 2);
+ ae.margin->add_theme_constant_override("margin_right", min_size.x / 2);
+ ae.margin->add_theme_constant_override("margin_bottom", min_size.y / 2);
+ }
+ ae.panel->add_child(ae.margin);
+
+ ae.hbox = memnew(HBoxContainer);
+ ae.hbox->set_h_size_flags(SIZE_EXPAND_FILL);
+ ae.hbox->set_v_size_flags(SIZE_EXPAND_FILL);
+ ae.margin->add_child(ae.hbox);
+
+ // Move button.
+ ae.move_texture_rect = memnew(TextureRect);
+ ae.move_texture_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
+ if (is_inside_tree()) {
+ ae.move_texture_rect->set_texture(get_theme_icon(SNAME("TripleBar"), SNAME("EditorIcons")));
+ }
+ ae.hbox->add_child(ae.move_texture_rect);
+
+ // Right vbox.
+ ae.vbox = memnew(VBoxContainer);
+ ae.vbox->set_h_size_flags(SIZE_EXPAND_FILL);
+ ae.vbox->set_v_size_flags(SIZE_EXPAND_FILL);
+ ae.hbox->add_child(ae.vbox);
+ }
+
+ // Hide/show the add button.
+ add_button->set_visible(page == max_page);
+
+ if (max_page == 0) {
+ hbox_pagination->hide();
+ } else {
+ // Update buttons.
+ first_page_button->set_disabled(page == 0);
+ prev_page_button->set_disabled(page == 0);
+ next_page_button->set_disabled(page == max_page);
+ last_page_button->set_disabled(page == max_page);
+
+ // Update page number and page count.
+ page_line_edit->set_text(vformat("%d", page + 1));
+ page_count_label->set_text(vformat("/ %d", max_page + 1));
+ }
+}
+
+Variant EditorInspectorArray::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
+ int index = p_from->get_meta("index");
+ Dictionary dict;
+ dict["type"] = "property_array_element";
+ dict["property_array_prefix"] = array_element_prefix;
+ dict["index"] = index;
+
+ return dict;
+}
+
+void EditorInspectorArray::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
+ Dictionary dict = p_data;
+
+ int to_drop = dict["index"];
+ int drop_position = _drop_position();
+ if (drop_position < 0) {
+ return;
+ }
+ _move_element(to_drop, begin_array_index + drop_position);
+}
+
+bool EditorInspectorArray::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
+ // First, update drawing.
+ control_dropping->update();
+
+ if (p_data.get_type() != Variant::DICTIONARY) {
+ return false;
+ }
+ Dictionary dict = p_data;
+ int drop_position = _drop_position();
+ if (!dict.has("type") || dict["type"] != "property_array_element" || String(dict["property_array_prefix"]) != array_element_prefix || drop_position < 0) {
+ return false;
+ }
+
+ // Check in dropping at the given index does indeed move the item.
+ int moved_array_index = (int)dict["index"];
+ int drop_array_index = begin_array_index + drop_position;
+
+ return drop_array_index != moved_array_index && drop_array_index - 1 != moved_array_index;
+}
+
+void EditorInspectorArray::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ Color color = get_theme_color(SNAME("dark_color_1"), SNAME("Editor"));
+ odd_style->set_bg_color(color.lightened(0.15));
+ even_style->set_bg_color(color.darkened(0.15));
+
+ for (int i = 0; i < (int)array_elements.size(); i++) {
+ ArrayElement &ae = array_elements[i];
+ ae.move_texture_rect->set_texture(get_theme_icon(SNAME("TripleBar"), SNAME("EditorIcons")));
+
+ Size2 min_size = get_theme_stylebox("Focus", "EditorStyles")->get_minimum_size();
+ ae.margin->add_theme_constant_override("margin_left", min_size.x / 2);
+ ae.margin->add_theme_constant_override("margin_top", min_size.y / 2);
+ ae.margin->add_theme_constant_override("margin_right", min_size.x / 2);
+ ae.margin->add_theme_constant_override("margin_bottom", min_size.y / 2);
+ }
+
+ add_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
+ first_page_button->set_icon(get_theme_icon(SNAME("PageFirst"), SNAME("EditorIcons")));
+ prev_page_button->set_icon(get_theme_icon(SNAME("PagePrevious"), SNAME("EditorIcons")));
+ next_page_button->set_icon(get_theme_icon(SNAME("PageNext"), SNAME("EditorIcons")));
+ last_page_button->set_icon(get_theme_icon(SNAME("PageLast"), SNAME("EditorIcons")));
+ minimum_size_changed();
+ } break;
+ case NOTIFICATION_DRAG_BEGIN: {
+ Dictionary dict = get_viewport()->gui_get_drag_data();
+ if (dict.has("type") && dict["type"] == "property_array_element" && String(dict["property_array_prefix"]) == array_element_prefix) {
+ dropping = true;
+ control_dropping->update();
+ }
+ } break;
+ case NOTIFICATION_DRAG_END: {
+ if (dropping) {
+ dropping = false;
+ control_dropping->update();
+ }
+ } break;
+ }
+}
+
+void EditorInspectorArray::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &EditorInspectorArray::get_drag_data_fw);
+ ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &EditorInspectorArray::can_drop_data_fw);
+ ClassDB::bind_method(D_METHOD("_drop_data_fw"), &EditorInspectorArray::drop_data_fw);
+
+ ADD_SIGNAL(MethodInfo("page_change_request"));
+}
+
+void EditorInspectorArray::set_undo_redo(UndoRedo *p_undo_redo) {
+ undo_redo = p_undo_redo;
+}
+
+void EditorInspectorArray::setup_with_move_element_function(Object *p_object, String p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable) {
+ count_property = "";
+ mode = MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION;
+ array_element_prefix = p_array_element_prefix;
+ page = p_page;
+
+ EditorInspectorSection::setup(String(p_array_element_prefix) + "_array", p_label, p_object, p_bg_color, p_foldable);
+
+ _setup();
+}
+
+void EditorInspectorArray::setup_with_count_property(Object *p_object, String p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable) {
+ count_property = p_count_property;
+ mode = MODE_USE_COUNT_PROPERTY;
+ array_element_prefix = p_array_element_prefix;
+ page = p_page;
+
+ EditorInspectorSection::setup(String(count_property) + "_array", p_label, p_object, p_bg_color, p_foldable);
+
+ _setup();
+}
+
+VBoxContainer *EditorInspectorArray::get_vbox(int p_index) {
+ if (p_index >= begin_array_index && p_index < end_array_index) {
+ return array_elements[p_index - begin_array_index].vbox;
+ } else if (p_index < 0) {
+ return vbox;
+ } else {
+ return nullptr;
+ }
+}
+
+EditorInspectorArray::EditorInspectorArray() {
+ set_mouse_filter(Control::MOUSE_FILTER_STOP);
+
+ odd_style.instantiate();
+ even_style.instantiate();
+
+ rmb_popup = memnew(PopupMenu);
+ rmb_popup->add_item(TTR("Move Up"), OPTION_MOVE_UP);
+ rmb_popup->add_item(TTR("Move Down"), OPTION_MOVE_DOWN);
+ rmb_popup->add_separator();
+ rmb_popup->add_item(TTR("Insert New Before"), OPTION_NEW_BEFORE);
+ rmb_popup->add_item(TTR("Insert New After"), OPTION_NEW_AFTER);
+ rmb_popup->add_separator();
+ rmb_popup->add_item(TTR("Remove"), OPTION_REMOVE);
+ rmb_popup->add_separator();
+ rmb_popup->add_item(TTR("Clear Array"), OPTION_CLEAR_ARRAY);
+ rmb_popup->add_item(TTR("Resize Array..."), OPTION_RESIZE_ARRAY);
+ rmb_popup->connect("id_pressed", callable_mp(this, &EditorInspectorArray::_rmb_popup_id_pressed));
+ add_child(rmb_popup);
+
+ elements_vbox = memnew(VBoxContainer);
+ elements_vbox->add_theme_constant_override("separation", 0);
+ vbox->add_child(elements_vbox);
+
+ add_button = memnew(Button);
+ add_button->set_text(TTR("Add Element"));
+ add_button->set_text_align(Button::ALIGN_CENTER);
+ add_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_add_button_pressed));
+ vbox->add_child(add_button);
+
+ hbox_pagination = memnew(HBoxContainer);
+ hbox_pagination->set_h_size_flags(SIZE_EXPAND_FILL);
+ hbox_pagination->set_alignment(HBoxContainer::ALIGN_CENTER);
+ vbox->add_child(hbox_pagination);
+
+ first_page_button = memnew(Button);
+ first_page_button->set_flat(true);
+ first_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_first_page_button_pressed));
+ hbox_pagination->add_child(first_page_button);
+
+ prev_page_button = memnew(Button);
+ prev_page_button->set_flat(true);
+ prev_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_prev_page_button_pressed));
+ hbox_pagination->add_child(prev_page_button);
+
+ page_line_edit = memnew(LineEdit);
+ page_line_edit->connect("text_submitted", callable_mp(this, &EditorInspectorArray::_page_line_edit_text_submitted));
+ page_line_edit->add_theme_constant_override("minimum_character_width", 2);
+ hbox_pagination->add_child(page_line_edit);
+
+ page_count_label = memnew(Label);
+ hbox_pagination->add_child(page_count_label);
+ next_page_button = memnew(Button);
+ next_page_button->set_flat(true);
+ next_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_next_page_button_pressed));
+ hbox_pagination->add_child(next_page_button);
+
+ last_page_button = memnew(Button);
+ last_page_button->set_flat(true);
+ last_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_last_page_button_pressed));
+ hbox_pagination->add_child(last_page_button);
+
+ control_dropping = memnew(Control);
+ control_dropping->connect("draw", callable_mp(this, &EditorInspectorArray::_control_dropping_draw));
+ control_dropping->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ add_child(control_dropping);
+
+ resize_dialog = memnew(AcceptDialog);
+ resize_dialog->set_title(TTRC("Resize Array"));
+ resize_dialog->add_cancel_button();
+ resize_dialog->connect("confirmed", callable_mp(this, &EditorInspectorArray::_resize_dialog_confirmed));
+ add_child(resize_dialog);
+
+ VBoxContainer *resize_dialog_vbox = memnew(VBoxContainer);
+ resize_dialog->add_child(resize_dialog_vbox);
+
+ new_size_line_edit = memnew(LineEdit);
+ new_size_line_edit->connect("text_changed", callable_mp(this, &EditorInspectorArray::_new_size_line_edit_text_changed));
+ new_size_line_edit->connect("text_submitted", callable_mp(this, &EditorInspectorArray::_new_size_line_edit_text_submitted));
+ resize_dialog_vbox->add_margin_child(TTRC("New Size:"), new_size_line_edit);
+
+ vbox->connect("visibility_changed", callable_mp(this, &EditorInspectorArray::_vbox_visibility_changed));
+}
+
////////////////////////////////////////////////
////////////////////////////////////////////////
@@ -1644,18 +2368,17 @@ void EditorInspector::update_tree() {
valid_plugins.push_back(inspector_plugins[i]);
}
+ // Decide if properties should be drawn in red.
bool draw_red = false;
-
if (is_inside_tree()) {
Node *nod = Object::cast_to(object);
Node *es = EditorNode::get_singleton()->get_edited_scene();
if (nod && es != nod && nod->get_owner() != es) {
+ // Draw in red edited nodes that are not in the currently edited scene.
draw_red = true;
}
}
- // TreeItem *current_category = nullptr;
-
String filter = search_box ? search_box->get_text() : "";
String group;
String group_base;
@@ -1667,30 +2390,30 @@ void EditorInspector::update_tree() {
object->get_property_list(&plist, true);
_update_script_class_properties(*object, plist);
- HashMap item_path;
- Map section_map;
-
- item_path[""] = main_vbox;
+ Map> vbox_per_path;
+ Map editor_inspector_array_per_prefix;
Color sscolor = get_theme_color(SNAME("prop_subsection"), SNAME("Editor"));
+ // Get the lists of editors to add the beginning.
for (Ref &ped : valid_plugins) {
ped->parse_begin(object);
_parse_added_editors(main_vbox, ped);
}
- for (List::Element *I = plist.front(); I; I = I->next()) {
- PropertyInfo &p = I->get();
-
- //make sure the property can be edited
+ // Get the lists of editors for properties.
+ for (List::Element *E_property = plist.front(); E_property; E_property = E_property->next()) {
+ PropertyInfo &p = E_property->get();
if (p.usage & PROPERTY_USAGE_SUBGROUP) {
+ // Setup a property sub-group.
subgroup = p.name;
subgroup_base = p.hint_string;
continue;
} else if (p.usage & PROPERTY_USAGE_GROUP) {
+ // Setup a property group.
group = p.name;
group_base = p.hint_string;
subgroup = "";
@@ -1699,6 +2422,7 @@ void EditorInspector::update_tree() {
continue;
} else if (p.usage & PROPERTY_USAGE_CATEGORY) {
+ // Setup a property category.
group = "";
group_base = "";
subgroup = "";
@@ -1708,9 +2432,9 @@ void EditorInspector::update_tree() {
continue;
}
- List::Element *N = I->next();
+ // Iterate over remaining properties. If no properties in category, skip the category.
+ List::Element *N = E_property->next();
bool valid = true;
- //if no properties in category, skip
while (N) {
if (N->get().usage & PROPERTY_USAGE_EDITOR && (!restrict_to_basic || (N->get().usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) {
break;
@@ -1722,28 +2446,32 @@ void EditorInspector::update_tree() {
N = N->next();
}
if (!valid) {
- continue; //empty, ignore
+ continue; // Empty, ignore it.
}
+ // Create an EditorInspectorCategory and add it to the inspector.
EditorInspectorCategory *category = memnew(EditorInspectorCategory);
main_vbox->add_child(category);
category_vbox = nullptr; //reset
String type = p.name;
+
+ // Set the category icon.
if (!ClassDB::class_exists(type) && !ScriptServer::is_global_class(type) && p.hint_string.length() && FileAccess::exists(p.hint_string)) {
- Ref