From 4bd7700e89f4476a3b2c76e7bed4263a111b3c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gilles=20Roudi=C3=A8re?= Date: Tue, 31 Aug 2021 10:48:45 +0200 Subject: [PATCH] Implement properties arrays in the Inspector. --- core/core_constants.cpp | 1 + core/object/class_db.cpp | 12 + core/object/class_db.h | 2 + core/object/object.h | 7 +- core/variant/callable_bind.cpp | 3 +- doc/classes/@GlobalScope.xml | 2 + doc/classes/TileMap.xml | 26 +- doc/classes/TileSet.xml | 159 +- editor/doc_tools.cpp | 2 +- editor/editor_data.cpp | 15 + editor/editor_data.h | 7 +- editor/editor_inspector.cpp | 1300 ++++++++++++++--- editor/editor_inspector.h | 128 +- editor/icons/PageFirst.svg | 47 + editor/icons/PageLast.svg | 47 + editor/icons/PageNext.svg | 42 + editor/icons/PagePrevious.svg | 42 + editor/plugins/tiles/tile_map_editor.cpp | 78 +- editor/plugins/tiles/tile_map_editor.h | 2 +- .../tiles/tile_set_atlas_source_editor.cpp | 2 +- editor/plugins/tiles/tile_set_editor.cpp | 225 ++- editor/plugins/tiles/tile_set_editor.h | 1 + scene/2d/tile_map.cpp | 58 +- scene/2d/tile_map.h | 4 +- scene/resources/tile_set.cpp | 693 +++++++-- scene/resources/tile_set.h | 88 +- tests/test_class_db.h | 2 +- 27 files changed, 2524 insertions(+), 471 deletions(-) create mode 100644 editor/icons/PageFirst.svg create mode 100644 editor/icons/PageLast.svg create mode 100644 editor/icons/PageNext.svg create mode 100644 editor/icons/PagePrevious.svg diff --git a/core/core_constants.cpp b/core/core_constants.cpp index 4f3f1fd16e6..3283d7fc44b 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 e268a8d292e..8e92340c1e5 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 166aa354690..e89c7fffd72 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 1838e7eb3d4..a44d921bff0 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 10446a5ec11..56eda6e703e 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 616d88c7f36..a18da1cf161 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 f3c64c3c7d8..e92a7331e6a 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 439c6e3830d..3e0ab873838 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 fee2dedddae..d04875f1880 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 c62e5b75b28..e40bbefef8f 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 df6ba9d0c96..9184ddcf394 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 03d1521baba..66f291bd3e2 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