summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGilles Roudière <gilles.roudiere@gmail.com>2021-08-31 10:48:45 +0200
committerGilles Roudière <gilles.roudiere@gmail.com>2021-09-07 09:51:28 +0200
commit4bd7700e89f4476a3b2c76e7bed4263a111b3c09 (patch)
tree4778e08bd64f9e2c7cbe1414a1acd1237b8f53ee
parentb0b30aaf41191ccbfdc8786cb79a76efb16d1459 (diff)
Implement properties arrays in the Inspector.
-rw-r--r--core/core_constants.cpp1
-rw-r--r--core/object/class_db.cpp12
-rw-r--r--core/object/class_db.h2
-rw-r--r--core/object/object.h7
-rw-r--r--core/variant/callable_bind.cpp3
-rw-r--r--doc/classes/@GlobalScope.xml2
-rw-r--r--doc/classes/TileMap.xml26
-rw-r--r--doc/classes/TileSet.xml159
-rw-r--r--editor/doc_tools.cpp2
-rw-r--r--editor/editor_data.cpp15
-rw-r--r--editor/editor_data.h7
-rw-r--r--editor/editor_inspector.cpp1278
-rw-r--r--editor/editor_inspector.h128
-rw-r--r--editor/icons/PageFirst.svg47
-rw-r--r--editor/icons/PageLast.svg47
-rw-r--r--editor/icons/PageNext.svg42
-rw-r--r--editor/icons/PagePrevious.svg42
-rw-r--r--editor/plugins/tiles/tile_map_editor.cpp80
-rw-r--r--editor/plugins/tiles/tile_map_editor.h2
-rw-r--r--editor/plugins/tiles/tile_set_atlas_source_editor.cpp2
-rw-r--r--editor/plugins/tiles/tile_set_editor.cpp225
-rw-r--r--editor/plugins/tiles/tile_set_editor.h1
-rw-r--r--scene/2d/tile_map.cpp58
-rw-r--r--scene/2d/tile_map.h4
-rw-r--r--scene/resources/tile_set.cpp689
-rw-r--r--scene/resources/tile_set.h88
-rw-r--r--tests/test_class_db.h2
27 files changed, 2512 insertions, 459 deletions
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 @@
</constant>
<constant name="PROPERTY_USAGE_EDITOR_BASIC_SETTING" value="268435456" enum="PropertyUsageFlags">
</constant>
+ <constant name="PROPERTY_USAGE_ARRAY" value="1073741824" enum="PropertyUsageFlags">
+ </constant>
<constant name="PROPERTY_USAGE_DEFAULT" value="7" enum="PropertyUsageFlags">
Default usage (storage, editor and network).
</constant>
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 @@
<link title="2D Kinematic Character Demo">https://godotengine.org/asset-library/asset/113</link>
</tutorials>
<methods>
+ <method name="add_layer">
+ <return type="void" />
+ <argument index="0" name="arg0" type="int" />
+ <description>
+ </description>
+ </method>
<method name="clear">
<return type="void" />
<description>
@@ -71,6 +77,11 @@
<description>
</description>
</method>
+ <method name="get_layers_count" qualifiers="const">
+ <return type="int" />
+ <description>
+ </description>
+ </method>
<method name="get_neighbor_cell" qualifiers="const">
<return type="Vector2i" />
<argument index="0" name="coords" type="Vector2i" />
@@ -116,6 +127,19 @@
Returns the local position corresponding to the given tilemap (grid-based) coordinates.
</description>
</method>
+ <method name="move_layer">
+ <return type="void" />
+ <argument index="0" name="arg0" type="int" />
+ <argument index="1" name="arg1" type="int" />
+ <description>
+ </description>
+ </method>
+ <method name="remove_layer">
+ <return type="void" />
+ <argument index="0" name="arg0" type="int" />
+ <description>
+ </description>
+ </method>
<method name="set_cell">
<return type="void" />
<argument index="0" name="layer" type="int" />
@@ -176,8 +200,6 @@
</member>
<member name="collision_visibility_mode" type="int" setter="set_collision_visibility_mode" getter="get_collision_visibility_mode" enum="TileMap.VisibilityMode" default="0">
</member>
- <member name="layers_count" type="int" setter="set_layers_count" getter="get_layers_count" default="1">
- </member>
<member name="navigation_visibility_mode" type="int" setter="set_navigation_visibility_mode" getter="get_navigation_visibility_mode" enum="TileMap.VisibilityMode" default="0">
</member>
<member name="tile_set" type="TileSet" setter="set_tileset" getter="get_tileset">
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 @@
<link title="2D Kinematic Character Demo">https://godotengine.org/asset-library/asset/113</link>
</tutorials>
<methods>
+ <method name="add_custom_data_layer">
+ <return type="void" />
+ <argument index="0" name="to_position" type="int" default="-1" />
+ <description>
+ </description>
+ </method>
+ <method name="add_navigation_layer">
+ <return type="void" />
+ <argument index="0" name="to_position" type="int" default="-1" />
+ <description>
+ </description>
+ </method>
+ <method name="add_occlusion_layer">
+ <return type="void" />
+ <argument index="0" name="to_position" type="int" default="-1" />
+ <description>
+ </description>
+ </method>
+ <method name="add_physics_layer">
+ <return type="void" />
+ <argument index="0" name="to_position" type="int" default="-1" />
+ <description>
+ </description>
+ </method>
<method name="add_source">
<return type="int" />
<argument index="0" name="atlas_source_id_override" type="TileSetSource" />
@@ -24,6 +48,19 @@
<description>
</description>
</method>
+ <method name="add_terrain">
+ <return type="void" />
+ <argument index="0" name="terrain_set" type="int" />
+ <argument index="1" name="to_position" type="int" default="-1" />
+ <description>
+ </description>
+ </method>
+ <method name="add_terrain_set">
+ <return type="void" />
+ <argument index="0" name="to_position" type="int" default="-1" />
+ <description>
+ </description>
+ </method>
<method name="cleanup_invalid_tile_proxies">
<return type="void" />
<description>
@@ -49,12 +86,22 @@
<description>
</description>
</method>
+ <method name="get_custom_data_layers_count" qualifiers="const">
+ <return type="int" />
+ <description>
+ </description>
+ </method>
<method name="get_navigation_layer_layers" qualifiers="const">
<return type="int" />
<argument index="0" name="layer_index" type="int" />
<description>
</description>
</method>
+ <method name="get_navigation_layers_count" qualifiers="const">
+ <return type="int" />
+ <description>
+ </description>
+ </method>
<method name="get_next_source_id" qualifiers="const">
<return type="int" />
<description>
@@ -72,6 +119,11 @@
<description>
</description>
</method>
+ <method name="get_occlusion_layers_count" qualifiers="const">
+ <return type="int" />
+ <description>
+ </description>
+ </method>
<method name="get_physics_layer_collision_layer" qualifiers="const">
<return type="int" />
<argument index="0" name="layer_index" type="int" />
@@ -90,6 +142,11 @@
<description>
</description>
</method>
+ <method name="get_physics_layers_count" qualifiers="const">
+ <return type="int" />
+ <description>
+ </description>
+ </method>
<method name="get_source" qualifiers="const">
<return type="TileSetSource" />
<argument index="0" name="index" type="int" />
@@ -133,6 +190,11 @@
<description>
</description>
</method>
+ <method name="get_terrain_sets_count" qualifiers="const">
+ <return type="int" />
+ <description>
+ </description>
+ </method>
<method name="get_terrains_count" qualifiers="const">
<return type="int" />
<argument index="0" name="terrain_set" type="int" />
@@ -174,6 +236,49 @@
<description>
</description>
</method>
+ <method name="move_custom_data_layer">
+ <return type="void" />
+ <argument index="0" name="layer_index" type="int" />
+ <argument index="1" name="to_position" type="int" />
+ <description>
+ </description>
+ </method>
+ <method name="move_navigation_layer">
+ <return type="void" />
+ <argument index="0" name="layer_index" type="int" />
+ <argument index="1" name="to_position" type="int" />
+ <description>
+ </description>
+ </method>
+ <method name="move_occlusion_layer">
+ <return type="void" />
+ <argument index="0" name="layer_index" type="int" />
+ <argument index="1" name="to_position" type="int" />
+ <description>
+ </description>
+ </method>
+ <method name="move_physics_layer">
+ <return type="void" />
+ <argument index="0" name="layer_index" type="int" />
+ <argument index="1" name="to_position" type="int" />
+ <description>
+ </description>
+ </method>
+ <method name="move_terrain">
+ <return type="void" />
+ <argument index="0" name="terrain_set" type="int" />
+ <argument index="1" name="terrain_index" type="int" />
+ <argument index="2" name="to_position" type="int" />
+ <description>
+ </description>
+ </method>
+ <method name="move_terrain_set">
+ <return type="void" />
+ <argument index="0" name="layer_index" type="int" />
+ <argument index="1" name="to_position" type="int" />
+ <description>
+ </description>
+ </method>
<method name="remove_alternative_level_tile_proxy">
<return type="void" />
<argument index="0" name="source_from" type="int" />
@@ -189,6 +294,30 @@
<description>
</description>
</method>
+ <method name="remove_custom_data_layer">
+ <return type="void" />
+ <argument index="0" name="layer_index" type="int" />
+ <description>
+ </description>
+ </method>
+ <method name="remove_navigation_layer">
+ <return type="void" />
+ <argument index="0" name="layer_index" type="int" />
+ <description>
+ </description>
+ </method>
+ <method name="remove_occlusion_layer">
+ <return type="void" />
+ <argument index="0" name="layer_index" type="int" />
+ <description>
+ </description>
+ </method>
+ <method name="remove_physics_layer">
+ <return type="void" />
+ <argument index="0" name="layer_index" type="int" />
+ <description>
+ </description>
+ </method>
<method name="remove_source">
<return type="void" />
<argument index="0" name="source_id" type="int" />
@@ -201,6 +330,19 @@
<description>
</description>
</method>
+ <method name="remove_terrain">
+ <return type="void" />
+ <argument index="0" name="terrain_set" type="int" />
+ <argument index="1" name="terrain_index" type="int" />
+ <description>
+ </description>
+ </method>
+ <method name="remove_terrain_set">
+ <return type="void" />
+ <argument index="0" name="layer_index" type="int" />
+ <description>
+ </description>
+ </method>
<method name="set_alternative_level_tile_proxy">
<return type="void" />
<argument index="0" name="source_from" type="int" />
@@ -300,25 +442,8 @@
<description>
</description>
</method>
- <method name="set_terrains_count">
- <return type="void" />
- <argument index="0" name="terrain_set" type="int" />
- <argument index="1" name="terrains_count" type="int" />
- <description>
- </description>
- </method>
</methods>
<members>
- <member name="custom_data_layers_count" type="int" setter="set_custom_data_layers_count" getter="get_custom_data_layers_count" default="0">
- </member>
- <member name="navigation_layers_count" type="int" setter="set_navigation_layers_count" getter="get_navigation_layers_count" default="0">
- </member>
- <member name="occlusion_layers_count" type="int" setter="set_occlusion_layers_count" getter="get_occlusion_layers_count" default="0">
- </member>
- <member name="physics_layers_count" type="int" setter="set_physics_layers_count" getter="get_physics_layers_count" default="0">
- </member>
- <member name="terrains_sets_count" type="int" setter="set_terrain_sets_count" getter="get_terrain_sets_count" default="0">
- </member>
<member name="tile_layout" type="int" setter="set_tile_layout" getter="get_tile_layout" enum="TileSet.TileLayout" default="0">
</member>
<member name="tile_offset_axis" type="int" setter="set_tile_offset_axis" getter="get_tile_offset_axis" enum="TileSet.TileOffsetAxis" default="0">
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<Callable> 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<PropertyData> clipboard;
UndoRedo undo_redo;
Vector<Callable> undo_redo_callbacks;
+ Map<StringName, Callable> 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<Callable> 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> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts"));
- int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts"));
-
- Ref<Texture2D> 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"));
+ 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> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts"));
+ int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts"));
+
+ // Get the right direction arrow texture, if the section is foldable.
+ Ref<Texture2D> 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<Control>(get_child(i));
- if (!c) {
- continue;
- }
- if (c->is_set_as_top_level()) {
- continue;
- }
- if (!c->is_visible_in_tree()) {
- 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());
}
+ header_height += get_theme_constant(SNAME("vseparation"), SNAME("Tree"));
- fit_child_in_rect(c, rect);
- }
-
- update(); //need to redraw text
- }
-
- if (p_what == NOTIFICATION_DRAW) {
- Ref<Texture2D> arrow;
- bool rtl = is_layout_rtl();
+ 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<Control>(get_child(i));
+ if (!c) {
+ continue;
+ }
+ if (c->is_set_as_top_level()) {
+ continue;
+ }
- 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"));
+ fit_child_in_rect(c, Rect2(offset, size));
+ }
+ } break;
+ case NOTIFICATION_DRAW: {
+ // Get the section header font.
+ Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts"));
+ int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts"));
+
+ // Get the right direction arrow texture, if the section is foldable.
+ Ref<Texture2D> 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> 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<Control>(vbox->get_child(child_idx));
+ 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());
+ }
+ }
- // 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 (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();
- dropping = children_can_drop;
- 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<Control>(vbox->get_child(child_idx));
- if (p_what == NOTIFICATION_DRAG_END) {
- dropping = false;
- update();
- }
+ // 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_ENTER) {
- if (dropping) {
- dropping_unfold_timer->start();
- }
- }
+ 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;
- if (p_what == NOTIFICATION_MOUSE_EXIT) {
- if (dropping) {
- dropping_unfold_timer->stop();
- }
+ 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);
@@ -1459,6 +1457,732 @@ EditorInspectorSection::~EditorInspectorSection() {
////////////////////////////////////////////////
////////////////////////////////////////////////
+int EditorInspectorArray::_get_array_count() {
+ if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
+ List<PropertyInfo> 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<Control>(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<Control>(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<StyleBox> 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<InputEvent> p_event, int p_index) {
+ ERR_FAIL_INDEX(p_index, (int)array_elements.size());
+
+ Ref<InputEventKey> 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<InputEventMouseButton> 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<PropertyInfo> 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<PropertyInfo> 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<PropertyInfo> 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<PropertyInfo> &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));
+}
+
+////////////////////////////////////////////////
+////////////////////////////////////////////////
Ref<EditorInspectorPlugin> EditorInspector::inspector_plugins[MAX_PLUGINS];
int EditorInspector::inspector_plugin_count = 0;
@@ -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<Node>(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<String, VBoxContainer *> item_path;
- Map<VBoxContainer *, EditorInspectorSection *> section_map;
-
- item_path[""] = main_vbox;
+ Map<VBoxContainer *, HashMap<String, VBoxContainer *>> vbox_per_path;
+ Map<String, EditorInspectorArray *> 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<EditorInspectorPlugin> &ped : valid_plugins) {
ped->parse_begin(object);
_parse_added_editors(main_vbox, ped);
}
- for (List<PropertyInfo>::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<PropertyInfo>::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<PropertyInfo>::Element *N = I->next();
+ // Iterate over remaining properties. If no properties in category, skip the category.
+ List<PropertyInfo>::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<Script> s = ResourceLoader::load(p.hint_string, "Script");
+ // If we have a category inside a script, search for the first script with a valid icon.
+ Ref<Script> script = ResourceLoader::load(p.hint_string, "Script");
String base_type;
- if (s.is_valid()) {
- base_type = s->get_instance_base_type();
+ if (script.is_valid()) {
+ base_type = script->get_instance_base_type();
}
- while (s.is_valid()) {
- StringName name = EditorNode::get_editor_data().script_class_get_name(s->get_path());
+ while (script.is_valid()) {
+ StringName name = EditorNode::get_editor_data().script_class_get_name(script->get_path());
String icon_path = EditorNode::get_editor_data().script_class_get_icon_path(name);
if (name != StringName() && icon_path.length()) {
category->icon = ResourceLoader::load(icon_path, "Texture");
break;
}
- s = s->get_base_script();
+ script = script->get_base_script();
}
if (category->icon.is_null() && has_theme_icon(base_type, SNAME("EditorIcons"))) {
category->icon = get_theme_icon(base_type, SNAME("EditorIcons"));
@@ -1754,9 +2482,12 @@ void EditorInspector::update_tree() {
category->icon = EditorNode::get_singleton()->get_class_icon(type, "Object");
}
}
+
+ // Set the category label.
category->label = type;
if (use_doc_hints) {
+ // Sets the category tooltip to show documentation.
StringName type2 = p.name;
if (!class_descr_cache.has(type2)) {
String descr;
@@ -1771,6 +2502,7 @@ void EditorInspector::update_tree() {
category->set_tooltip(p.name + "::" + (class_descr_cache[type2] == "" ? "" : class_descr_cache[type2]));
}
+ // Add editors at the start of a category.
for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {
ped->parse_category(object, p.name);
_parse_added_editors(main_vbox, ped);
@@ -1779,134 +2511,215 @@ void EditorInspector::update_tree() {
continue;
} else if (!(p.usage & PROPERTY_USAGE_EDITOR) || _is_property_disabled_by_feature_profile(p.name) || (restrict_to_basic && !(p.usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) {
+ // Ignore properties that are not supposed to be in the inspector.
continue;
}
if (p.name == "script") {
- category_vbox = nullptr; // script should go into its own category
+ // Script should go into its own category.
+ category_vbox = nullptr;
}
if (p.usage & PROPERTY_USAGE_HIGH_END_GFX && RS::get_singleton()->is_low_end()) {
- continue; //do not show this property in low end gfx
+ // Do not show this property in low end gfx.
+ continue;
}
if (p.name == "script" && (hide_script || bool(object->call("_hide_script_from_inspector")))) {
+ // Hide script variables from inspector if required.
continue;
}
- String basename = p.name;
+ // Get the path for property.
+ String path = p.name;
- if (subgroup != "") {
- if (subgroup_base != "") {
- if (basename.begins_with(subgroup_base)) {
- basename = basename.replace_first(subgroup_base, "");
- } else if (subgroup_base.begins_with(basename)) {
- //keep it, this is used pretty often
- } else {
- subgroup = ""; //no longer using subgroup base, clear
+ // First check if we have an array that fits the prefix.
+ String array_prefix = "";
+ int array_index = -1;
+ for (Map<String, EditorInspectorArray *>::Element *E = editor_inspector_array_per_prefix.front(); E; E = E->next()) {
+ if (p.name.begins_with(E->key()) && E->key().length() > array_prefix.length()) {
+ array_prefix = E->key();
+ }
+ }
+
+ if (!array_prefix.is_empty()) {
+ // If we have an array element, find the according index in array.
+ String str = p.name.trim_prefix(array_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) {
+ array_index = str.left(to_char_index).to_int();
+ } else {
+ array_prefix = "";
}
}
- if (group != "") {
- if (group_base != "" && subgroup == "") {
- if (basename.begins_with(group_base)) {
- basename = basename.replace_first(group_base, "");
- } else if (group_base.begins_with(basename)) {
- //keep it, this is used pretty often
+
+ if (!array_prefix.is_empty()) {
+ path = path.trim_prefix(array_prefix);
+ int char_index = path.find("/");
+ if (char_index >= 0) {
+ path = path.right(-char_index - 1);
+ } else {
+ path = vformat(TTR("Element %s"), array_index);
+ }
+ } else {
+ // Check if we exit or not a subgroup. If there is a prefix, remove it from the property label string.
+ if (subgroup != "" && subgroup_base != "") {
+ if (path.begins_with(subgroup_base)) {
+ path = path.trim_prefix(subgroup_base);
+ } else if (subgroup_base.begins_with(path)) {
+ // Keep it, this is used pretty often.
} else {
- group = ""; //no longer using group base, clear
+ subgroup = ""; // The prefix changed, we are no longer in the subgroup.
+ }
+ }
+
+ // Check if we exit or not a group. If there is a prefix, remove it from the property label string.
+ if (group != "" && group_base != "" && subgroup == "") {
+ if (path.begins_with(group_base)) {
+ path = path.trim_prefix(group_base);
+ } else if (group_base.begins_with(path)) {
+ // Keep it, this is used pretty often.
+ } else {
+ group = ""; // The prefix changed, we are no longer in the group.
subgroup = "";
}
}
- }
- if (subgroup != "") {
- basename = subgroup + "/" + basename;
- }
- if (group != "") {
- basename = group + "/" + basename;
- }
- String name = (basename.find("/") != -1) ? basename.substr(basename.rfind("/") + 1) : basename;
+ // Add the group and subgroup to the path.
+ if (subgroup != "") {
+ path = subgroup + "/" + path;
+ }
+ if (group != "") {
+ path = group + "/" + path;
+ }
+ }
+ // Get the property label's string.
+ String property_label_string = (path.find("/") != -1) ? path.substr(path.rfind("/") + 1) : path;
if (capitalize_paths) {
- int dot = name.find(".");
+ // Capitalize paths.
+ int dot = property_label_string.find(".");
if (dot != -1) {
- String ov = name.substr(dot);
- name = name.substr(0, dot);
- name = name.capitalize();
- name += ov;
-
+ String ov = property_label_string.substr(dot);
+ property_label_string = property_label_string.substr(0, dot);
+ property_label_string = property_label_string.capitalize();
+ property_label_string += ov;
} else {
- name = name.capitalize();
+ property_label_string = property_label_string.capitalize();
}
}
- String path;
- {
- int idx = basename.rfind("/");
- if (idx > -1) {
- path = basename.left(idx);
- }
+ // Remove the property from the path.
+ int idx = path.rfind("/");
+ if (idx > -1) {
+ path = path.left(idx);
+ } else {
+ path = "";
}
+ // Ignore properties that do not fit the filter.
if (use_filter && filter != "") {
- String cat = path;
-
- if (capitalize_paths) {
- cat = cat.capitalize();
- }
-
- if (!filter.is_subsequence_ofi(cat) && !filter.is_subsequence_ofi(name) && property_prefix.to_lower().find(filter.to_lower()) == -1) {
+ if (!filter.is_subsequence_ofi(path) && !filter.is_subsequence_ofi(property_label_string) && property_prefix.to_lower().find(filter.to_lower()) == -1) {
continue;
}
}
+ // Recreate the category vbox if it was reset.
if (category_vbox == nullptr) {
category_vbox = memnew(VBoxContainer);
main_vbox->add_child(category_vbox);
}
- VBoxContainer *current_vbox = main_vbox;
+ // Find the correct section/vbox to add the property editor to.
+ VBoxContainer *root_vbox = array_prefix.is_empty() ? main_vbox : editor_inspector_array_per_prefix[array_prefix]->get_vbox(array_index);
+ if (!root_vbox) {
+ continue;
+ }
- {
- String acc_path = "";
- int level = 1;
- for (int i = 0; i < path.get_slice_count("/"); i++) {
- String path_name = path.get_slice("/", i);
- if (i > 0) {
- acc_path += "/";
- }
- acc_path += path_name;
- if (!item_path.has(acc_path)) {
- EditorInspectorSection *section = memnew(EditorInspectorSection);
- current_vbox->add_child(section);
- sections.push_back(section);
-
- if (capitalize_paths) {
- path_name = path_name.capitalize();
- }
+ if (!vbox_per_path.has(root_vbox)) {
+ vbox_per_path[root_vbox] = HashMap<String, VBoxContainer *>();
+ vbox_per_path[root_vbox][""] = root_vbox;
+ }
+
+ VBoxContainer *current_vbox = root_vbox;
+ String acc_path = "";
+ int level = 1;
- Color c = sscolor;
- c.a /= level;
- section->setup(acc_path, path_name, object, c, use_folding);
+ Vector<String> components = path.split("/");
+ for (int i = 0; i < components.size(); i++) {
+ String component = components[i];
+ acc_path += (i > 0) ? "/" + component : component;
- VBoxContainer *vb = section->get_vbox();
- item_path[acc_path] = vb;
- section_map[vb] = section;
+ if (!vbox_per_path[root_vbox].has(acc_path)) {
+ // If the section does not exists, create it.
+ EditorInspectorSection *section = memnew(EditorInspectorSection);
+ current_vbox->add_child(section);
+ sections.push_back(section);
+
+ if (capitalize_paths) {
+ component = component.capitalize();
}
- current_vbox = item_path[acc_path];
- level = (MIN(level + 1, 4));
- }
- if (current_vbox == main_vbox) {
- //do not add directly to the main vbox, given it has no spacing
- if (category_vbox == nullptr) {
- category_vbox = memnew(VBoxContainer);
+ Color c = sscolor;
+ c.a /= level;
+ section->setup(acc_path, component, object, c, use_folding);
+
+ vbox_per_path[root_vbox][acc_path] = section->get_vbox();
+ }
+
+ current_vbox = vbox_per_path[root_vbox][acc_path];
+ level = (MIN(level + 1, 4));
+ }
+
+ // If we did not find a section to add the property to, add it to the category vbox instead (the category vbox handles margins correctly).
+ if (current_vbox == main_vbox) {
+ current_vbox = category_vbox;
+ }
+
+ // Check if the property is an array counter, if so create a dedicated array editor for the array.
+ if (p.usage & PROPERTY_USAGE_ARRAY) {
+ EditorInspectorArray *editor_inspector_array = nullptr;
+ StringName array_element_prefix;
+ Color c = sscolor;
+ c.a /= level;
+ if (p.type == Variant::NIL) {
+ // Setup the array to use a method to create/move/delete elements.
+ array_element_prefix = p.class_name;
+ editor_inspector_array = memnew(EditorInspectorArray);
+
+ String array_label = (path.find("/") != -1) ? path.substr(path.rfind("/") + 1) : path;
+ array_label = property_label_string.capitalize();
+ int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0;
+ editor_inspector_array->setup_with_move_element_function(object, array_label, array_element_prefix, page, c, use_folding);
+ editor_inspector_array->connect("page_change_request", callable_mp(this, &EditorInspector::_page_change_request), varray(array_element_prefix));
+ editor_inspector_array->set_undo_redo(undo_redo);
+ } else if (p.type == Variant::INT) {
+ // Setup the array to use the count property and built-in functions to create/move/delete elements.
+ Vector<String> class_name_components = String(p.class_name).split(",");
+ if (class_name_components.size() == 2) {
+ array_element_prefix = class_name_components[1];
+ editor_inspector_array = memnew(EditorInspectorArray);
+ int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0;
+ editor_inspector_array->setup_with_count_property(object, class_name_components[0], p.name, array_element_prefix, page, c, use_folding);
+ editor_inspector_array->connect("page_change_request", callable_mp(this, &EditorInspector::_page_change_request), varray(array_element_prefix));
+ editor_inspector_array->set_undo_redo(undo_redo);
}
- current_vbox = category_vbox;
}
+
+ if (editor_inspector_array) {
+ current_vbox->add_child(editor_inspector_array);
+ editor_inspector_array_per_prefix[array_element_prefix] = editor_inspector_array;
+ }
+ continue;
}
+ // Checkable and checked properties.
bool checkable = false;
bool checked = false;
if (p.usage & PROPERTY_USAGE_CHECKABLE) {
@@ -1916,6 +2729,7 @@ void EditorInspector::update_tree() {
bool property_read_only = (p.usage & PROPERTY_USAGE_READ_ONLY) || read_only;
+ // Mark properties that would require an editor restart (mostly when editing editor settings).
if (p.usage & PROPERTY_USAGE_RESTART_IF_CHANGED) {
restart_request_props.insert(p.name);
}
@@ -1923,14 +2737,19 @@ void EditorInspector::update_tree() {
String doc_hint;
if (use_doc_hints) {
+ // Build the doc hint, to use as tooltip.
+
+ // Get the class name.
StringName classname = object->get_class_name();
if (object_class != String()) {
classname = object_class;
}
+
StringName propname = property_prefix + p.name;
String descr;
bool found = false;
+ // Search for the property description in the cache.
Map<StringName, Map<StringName, String>>::Element *E = descr_cache.find(classname);
if (E) {
Map<StringName, String>::Element *F = E->get().find(propname);
@@ -1941,6 +2760,7 @@ void EditorInspector::update_tree() {
}
if (!found) {
+ // Build the property description String and add it to the cache.
DocTools *dd = EditorHelp::get_doc_data();
Map<String, DocData::ClassDoc>::Element *F = dd->class_list.find(classname);
while (F && descr == String()) {
@@ -1973,17 +2793,18 @@ void EditorInspector::update_tree() {
doc_hint = descr;
}
+ // Seach for the inspector plugin that will handle the properties. Then add the correct property editor to it.
for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {
bool exclusive = ped->parse_property(object, p.type, p.name, p.hint, p.hint_string, p.usage, wide_editors);
- List<EditorInspectorPlugin::AddedEditor> editors = ped->added_editors; //make a copy, since plugins may be used again in a sub-inspector
+ List<EditorInspectorPlugin::AddedEditor> editors = ped->added_editors; // Make a copy, since plugins may be used again in a sub-inspector.
ped->added_editors.clear();
for (const EditorInspectorPlugin::AddedEditor &F : editors) {
EditorProperty *ep = Object::cast_to<EditorProperty>(F.property_editor);
if (ep) {
- //set all this before the control gets the ENTER_TREE notification
+ // Set all this before the control gets the ENTER_TREE notification.
ep->object = object;
if (F.properties.size()) {
@@ -1998,7 +2819,7 @@ void EditorInspector::update_tree() {
ep->set_label(F.label);
} else {
// Use the existing one.
- ep->set_label(name);
+ ep->set_label(property_label_string);
}
for (int i = 0; i < F.properties.size(); i++) {
String prop = F.properties[i];
@@ -2021,6 +2842,7 @@ void EditorInspector::update_tree() {
current_vbox->add_child(F.property_editor);
if (ep) {
+ // Eventually, set other properties/signals after the property editor got added to the tree.
ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed));
if (p.usage & PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED) {
ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed_update_all), varray(), CONNECT_DEFERRED);
@@ -2049,17 +2871,17 @@ void EditorInspector::update_tree() {
}
if (exclusive) {
+ // If we know the plugin is exclusive, we don't need to go through other plugins.
break;
}
}
}
+ // Get the lists of to add at the end.
for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {
ped->parse_end();
_parse_added_editors(main_vbox, ped);
}
-
- //see if this property exists and should be kept
}
void EditorInspector::update_property(const String &p_prop) {
@@ -2098,6 +2920,7 @@ void EditorInspector::edit(Object *p_object) {
_clear();
object->disconnect("property_list_changed", callable_mp(this, &EditorInspector::_changed_callback));
}
+ per_array_page.clear();
object = p_object;
@@ -2243,6 +3066,15 @@ void EditorInspector::set_use_deletable_properties(bool p_enabled) {
deletable_properties = p_enabled;
}
+void EditorInspector::_page_change_request(int p_new_page, const StringName &p_array_prefix) {
+ int prev_page = per_array_page.has(p_array_prefix) ? per_array_page[p_array_prefix] : 0;
+ int new_page = MAX(0, p_new_page);
+ if (new_page != prev_page) {
+ per_array_page[p_array_prefix] = new_page;
+ update_tree_pending = true;
+ }
+}
+
void EditorInspector::_edit_request_change(Object *p_object, const String &p_property) {
if (object != p_object) { //may be undoing/redoing for a non edited object, so ignore
return;
@@ -2365,7 +3197,7 @@ void EditorInspector::_property_changed_update_all(const String &p_path, const V
update_tree();
}
-void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array p_values) {
+void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array p_values, bool p_changing) {
ERR_FAIL_COND(p_paths.size() == 0 || p_values.size() == 0);
ERR_FAIL_COND(p_paths.size() != p_values.size());
String names;
@@ -2382,9 +3214,13 @@ void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array
emit_signal(SNAME("restart_requested"));
}
}
- changing++;
+ if (p_changing) {
+ changing++;
+ }
undo_redo->commit_action();
- changing--;
+ if (p_changing) {
+ changing--;
+ }
}
void EditorInspector::_property_keyed(const String &p_path, bool p_advance) {
diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h
index b5d70d5454..f6b4303f38 100644
--- a/editor/editor_inspector.h
+++ b/editor/editor_inspector.h
@@ -32,8 +32,12 @@
#define EDITOR_INSPECTOR_H
#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
+#include "scene/gui/dialogs.h"
#include "scene/gui/line_edit.h"
+#include "scene/gui/panel_container.h"
#include "scene/gui/scroll_container.h"
+#include "scene/gui/texture_rect.h"
class UndoRedo;
@@ -251,9 +255,7 @@ class EditorInspectorSection : public Container {
String label;
String section;
- Object *object;
- VBoxContainer *vbox;
- bool vbox_added; //optimization
+ bool vbox_added; // Optimization.
Color bg_color;
bool foldable;
@@ -263,6 +265,9 @@ class EditorInspectorSection : public Container {
void _test_unfold();
protected:
+ Object *object;
+ VBoxContainer *vbox;
+
void _notification(int p_what);
static void _bind_methods();
virtual void gui_input(const Ref<InputEvent> &p_event) override;
@@ -281,6 +286,118 @@ public:
~EditorInspectorSection();
};
+class EditorInspectorArray : public EditorInspectorSection {
+ GDCLASS(EditorInspectorArray, EditorInspectorSection);
+
+ UndoRedo *undo_redo;
+
+ enum Mode {
+ MODE_NONE,
+ MODE_USE_COUNT_PROPERTY,
+ MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION,
+ } mode;
+ StringName count_property;
+ StringName array_element_prefix;
+
+ int count = 0;
+
+ VBoxContainer *elements_vbox;
+
+ Control *control_dropping;
+ bool dropping = false;
+
+ Button *add_button;
+
+ AcceptDialog *resize_dialog;
+ int new_size = 0;
+ LineEdit *new_size_line_edit;
+
+ // Pagination
+ int page_lenght = 5;
+ int page = 0;
+ int max_page = 0;
+ int begin_array_index = 0;
+ int end_array_index = 0;
+ HBoxContainer *hbox_pagination;
+ Button *first_page_button;
+ Button *prev_page_button;
+ LineEdit *page_line_edit;
+ Label *page_count_label;
+ Button *next_page_button;
+ Button *last_page_button;
+
+ enum MenuOptions {
+ OPTION_MOVE_UP = 0,
+ OPTION_MOVE_DOWN,
+ OPTION_NEW_BEFORE,
+ OPTION_NEW_AFTER,
+ OPTION_REMOVE,
+ OPTION_CLEAR_ARRAY,
+ OPTION_RESIZE_ARRAY,
+ };
+ int popup_array_index_pressed = -1;
+ PopupMenu *rmb_popup;
+
+ struct ArrayElement {
+ PanelContainer *panel;
+ MarginContainer *margin;
+ HBoxContainer *hbox;
+ TextureRect *move_texture_rect;
+ VBoxContainer *vbox;
+ };
+ LocalVector<ArrayElement> array_elements;
+
+ Ref<StyleBoxFlat> odd_style;
+ Ref<StyleBoxFlat> even_style;
+
+ int _get_array_count();
+ void _add_button_pressed();
+
+ void _first_page_button_pressed();
+ void _prev_page_button_pressed();
+ void _page_line_edit_text_submitted(String p_text);
+ void _next_page_button_pressed();
+ void _last_page_button_pressed();
+
+ void _rmb_popup_id_pressed(int p_id);
+
+ void _control_dropping_draw();
+
+ void _vbox_visibility_changed();
+
+ void _panel_draw(int p_index);
+ void _panel_gui_input(Ref<InputEvent> p_event, int p_index);
+ void _move_element(int p_element_index, int p_to_pos);
+ void _clear_array();
+ void _resize_array(int p_size);
+ Array _extract_properties_as_array(const List<PropertyInfo> &p_list);
+ int _drop_position() const;
+
+ void _new_size_line_edit_text_changed(String p_text);
+ void _new_size_line_edit_text_submitted(String p_text);
+ void _resize_dialog_confirmed();
+
+ void _update_elements_visibility();
+ void _setup();
+
+ Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
+ void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
+ bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ void set_undo_redo(UndoRedo *p_undo_redo);
+
+ void 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);
+ void 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);
+ VBoxContainer *get_vbox(int p_index);
+
+ EditorInspectorArray();
+};
+
class EditorInspector : public ScrollContainer {
GDCLASS(EditorInspector, ScrollContainer);
@@ -342,7 +459,7 @@ class EditorInspector : public ScrollContainer {
void _property_changed(const String &p_path, const Variant &p_value, const String &p_name = "", bool p_changing = false);
void _property_changed_update_all(const String &p_path, const Variant &p_value, const String &p_name = "", bool p_changing = false);
- void _multiple_properties_changed(Vector<String> p_paths, Array p_values);
+ void _multiple_properties_changed(Vector<String> p_paths, Array p_values, bool p_changing = false);
void _property_keyed(const String &p_path, bool p_advance);
void _property_keyed_with_value(const String &p_path, const Variant &p_value, bool p_advance);
void _property_deleted(const String &p_path);
@@ -355,6 +472,9 @@ class EditorInspector : public ScrollContainer {
void _node_removed(Node *p_node);
+ Map<StringName, int> per_array_page;
+ void _page_change_request(int p_new_page, const StringName &p_array_prefix);
+
void _changed_callback();
void _edit_request_change(Object *p_object, const String &p_prop);
diff --git a/editor/icons/PageFirst.svg b/editor/icons/PageFirst.svg
new file mode 100644
index 0000000000..76078691ef
--- /dev/null
+++ b/editor/icons/PageFirst.svg
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ height="12"
+ viewBox="0 0 12 12"
+ width="12"
+ version="1.1"
+ id="svg4"
+ sodipodi:docname="PageFirst.svg"
+ inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ id="namedview6"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ showgrid="true"
+ inkscape:zoom="74.25"
+ inkscape:cx="18.053872"
+ inkscape:cy="6.5252525"
+ inkscape:window-width="3838"
+ inkscape:window-height="1582"
+ inkscape:window-x="0"
+ inkscape:window-y="16"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg4">
+ <inkscape:grid
+ type="xygrid"
+ id="grid989" />
+ </sodipodi:namedview>
+ <path
+ d="M 6,9 3,6 6,3"
+ style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ id="path2" />
+ <path
+ d="M 9,9 V 3"
+ style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ id="path2211"
+ sodipodi:nodetypes="cc" />
+</svg>
diff --git a/editor/icons/PageLast.svg b/editor/icons/PageLast.svg
new file mode 100644
index 0000000000..17c874e8c9
--- /dev/null
+++ b/editor/icons/PageLast.svg
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ height="12"
+ viewBox="0 0 12 12"
+ width="12"
+ version="1.1"
+ id="svg4"
+ sodipodi:docname="PageLast.svg"
+ inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ id="namedview6"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ showgrid="true"
+ inkscape:zoom="74.25"
+ inkscape:cx="18.053872"
+ inkscape:cy="6.5252525"
+ inkscape:window-width="3838"
+ inkscape:window-height="1582"
+ inkscape:window-x="0"
+ inkscape:window-y="16"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg4">
+ <inkscape:grid
+ type="xygrid"
+ id="grid989" />
+ </sodipodi:namedview>
+ <path
+ d="m 6.0000414,9 3,-3 -3,-3"
+ style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ id="path2" />
+ <path
+ d="M 3.0000414,9 V 3"
+ style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ id="path2211"
+ sodipodi:nodetypes="cc" />
+</svg>
diff --git a/editor/icons/PageNext.svg b/editor/icons/PageNext.svg
new file mode 100644
index 0000000000..89ff6219bb
--- /dev/null
+++ b/editor/icons/PageNext.svg
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ height="12"
+ viewBox="0 0 12 12"
+ width="12"
+ version="1.1"
+ id="svg4"
+ sodipodi:docname="PageNext.svg"
+ inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ id="namedview6"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ showgrid="true"
+ inkscape:zoom="105.00536"
+ inkscape:cx="4.5854803"
+ inkscape:cy="5.9377923"
+ inkscape:window-width="3838"
+ inkscape:window-height="1582"
+ inkscape:window-x="0"
+ inkscape:window-y="16"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg4">
+ <inkscape:grid
+ type="xygrid"
+ id="grid989" />
+ </sodipodi:namedview>
+ <path
+ d="m 4.5000207,9 3,-3 -3,-3"
+ style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ id="path2" />
+</svg>
diff --git a/editor/icons/PagePrevious.svg b/editor/icons/PagePrevious.svg
new file mode 100644
index 0000000000..a2fa84da0c
--- /dev/null
+++ b/editor/icons/PagePrevious.svg
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ height="12"
+ viewBox="0 0 12 12"
+ width="12"
+ version="1.1"
+ id="svg4"
+ sodipodi:docname="PagePrevious.svg"
+ inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ id="namedview6"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ showgrid="true"
+ inkscape:zoom="105.00536"
+ inkscape:cx="4.5854803"
+ inkscape:cy="5.9377923"
+ inkscape:window-width="3838"
+ inkscape:window-height="1582"
+ inkscape:window-x="0"
+ inkscape:window-y="16"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg4">
+ <inkscape:grid
+ type="xygrid"
+ id="grid989" />
+ </sodipodi:namedview>
+ <path
+ d="m 7.4999793,9 -3,-3 3,-3"
+ style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ id="path2" />
+</svg>
diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp
index 77084f551a..b5e070b4d6 100644
--- a/editor/plugins/tiles/tile_map_editor.cpp
+++ b/editor/plugins/tiles/tile_map_editor.cpp
@@ -3549,30 +3549,76 @@ void TileMapEditor::_update_layers_selection() {
tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id, tile_map_layer);
}
-void TileMapEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) {
+void TileMapEditor::_move_tile_map_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos) {
UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo);
ERR_FAIL_COND(!undo_redo);
TileMap *tile_map = Object::cast_to<TileMap>(p_edited);
- if (tile_map) {
- if (p_property == "layers_count") {
- int new_layers_count = (int)p_new_value;
- if (new_layers_count < tile_map->get_layers_count()) {
- List<PropertyInfo> property_list;
- tile_map->get_property_list(&property_list);
-
- for (PropertyInfo property_info : property_list) {
- Vector<String> components = String(property_info.name).split("/", true, 2);
- if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) {
- int index = components[0].trim_prefix("layer_").to_int();
- if (index >= new_layers_count) {
- undo_redo->add_undo_property(tile_map, property_info.name, tile_map->get(property_info.name));
- }
- }
+ if (!tile_map) {
+ return;
+ }
+
+ // Compute the array indices to save.
+ int begin = 0;
+ int end;
+ if (p_array_prefix == "layer_") {
+ end = tile_map->get_layers_count();
+ } else {
+ ERR_FAIL_MSG("Invalid array prefix for TileSet.");
+ }
+ if (p_from_index < 0) {
+ // Adding new.
+ if (p_to_pos >= 0) {
+ begin = p_to_pos;
+ } else {
+ end = 0; // Nothing to save when adding at the end.
+ }
+ } else if (p_to_pos < 0) {
+ // Removing.
+ begin = p_from_index;
+ } else {
+ // Moving.
+ begin = MIN(p_from_index, p_to_pos);
+ end = MIN(MAX(p_from_index, p_to_pos) + 1, end);
+ }
+
+#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property));
+ // Save layers' properties.
+ if (p_from_index < 0) {
+ undo_redo->add_undo_method(tile_map, "remove_layer", p_to_pos < 0 ? tile_map->get_layers_count() : p_to_pos);
+ } else if (p_to_pos < 0) {
+ undo_redo->add_undo_method(tile_map, "add_layer", p_from_index);
+ }
+
+ List<PropertyInfo> properties;
+ tile_map->get_property_list(&properties);
+ for (PropertyInfo pi : properties) {
+ if (pi.name.begins_with(p_array_prefix)) {
+ String str = pi.name.trim_prefix(p_array_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();
+ if (array_index >= begin && array_index < end) {
+ ADD_UNDO(tile_map, pi.name);
}
}
}
}
+#undef ADD_UNDO
+
+ if (p_from_index < 0) {
+ undo_redo->add_do_method(tile_map, "add_layer", p_to_pos);
+ } else if (p_to_pos < 0) {
+ undo_redo->add_do_method(tile_map, "remove_layer", p_from_index);
+ } else {
+ undo_redo->add_do_method(tile_map, "move_layer", p_from_index, p_to_pos);
+ }
}
bool TileMapEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
@@ -3851,7 +3897,7 @@ TileMapEditor::TileMapEditor() {
_tab_changed(0);
// Registers UndoRedo inspector callback.
- EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileMapEditor::_undo_redo_inspector_callback));
+ EditorNode::get_singleton()->get_editor_data().add_move_array_element_function(SNAME("TileMap"), callable_mp(this, &TileMapEditor::_move_tile_map_array_element));
}
TileMapEditor::~TileMapEditor() {
diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h
index 6e2f2ce2ba..6126db59e9 100644
--- a/editor/plugins/tiles/tile_map_editor.h
+++ b/editor/plugins/tiles/tile_map_editor.h
@@ -341,7 +341,7 @@ private:
void _update_layers_selection();
// Inspector undo/redo callback.
- void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value);
+ void _move_tile_map_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos);
protected:
void _notification(int p_what);
diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
index 432f48fa85..c3a3f40e00 100644
--- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
+++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
@@ -1866,7 +1866,7 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo
UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo);
ERR_FAIL_COND(!undo_redo);
-#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, tile_data->get(property));
+#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property));
AtlasTileProxyObject *tile_data = Object::cast_to<AtlasTileProxyObject>(p_edited);
if (tile_data) {
diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp
index ba98a7d6b3..48d0d9b333 100644
--- a/editor/plugins/tiles/tile_set_editor.cpp
+++ b/editor/plugins/tiles/tile_set_editor.cpp
@@ -330,11 +330,192 @@ void TileSetEditor::_tile_set_changed() {
tile_set_changed_needs_update = true;
}
+void TileSetEditor::_move_tile_set_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos) {
+ UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo);
+ ERR_FAIL_COND(!undo_redo);
+
+ TileSet *tile_set = Object::cast_to<TileSet>(p_edited);
+ if (!tile_set) {
+ return;
+ }
+
+ Vector<String> components = String(p_array_prefix).split("/", true, 2);
+
+ // Compute the array indices to save.
+ int begin = 0;
+ int end;
+ if (p_array_prefix == "occlusion_layer_") {
+ end = tile_set->get_occlusion_layers_count();
+ } else if (p_array_prefix == "physics_layer_") {
+ end = tile_set->get_physics_layers_count();
+ } else if (p_array_prefix == "terrain_set_") {
+ end = tile_set->get_terrain_sets_count();
+ } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") {
+ int terrain_set = components[0].trim_prefix("terrain_set_").to_int();
+ end = tile_set->get_terrains_count(terrain_set);
+ } else if (p_array_prefix == "navigation_layer_") {
+ end = tile_set->get_navigation_layers_count();
+ } else if (p_array_prefix == "custom_data_layer_") {
+ end = tile_set->get_custom_data_layers_count();
+ } else {
+ ERR_FAIL_MSG("Invalid array prefix for TileSet.");
+ }
+ if (p_from_index < 0) {
+ // Adding new.
+ if (p_to_pos >= 0) {
+ begin = p_to_pos;
+ } else {
+ end = 0; // Nothing to save when adding at the end.
+ }
+ } else if (p_to_pos < 0) {
+ // Removing.
+ begin = p_from_index;
+ } else {
+ // Moving.
+ begin = MIN(p_from_index, p_to_pos);
+ end = MIN(MAX(p_from_index, p_to_pos) + 1, end);
+ }
+
+#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property));
+ // Save layers' properties.
+ List<PropertyInfo> properties;
+ tile_set->get_property_list(&properties);
+ for (PropertyInfo pi : properties) {
+ if (pi.name.begins_with(p_array_prefix)) {
+ String str = pi.name.trim_prefix(p_array_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();
+ if (array_index >= begin && array_index < end) {
+ ADD_UNDO(tile_set, pi.name);
+ }
+ }
+ }
+ }
+
+ // Save properties for TileSetAtlasSources tile data
+ for (int i = 0; i < tile_set->get_source_count(); i++) {
+ int source_id = tile_set->get_source_id(i);
+
+ Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id);
+ if (tas.is_valid()) {
+ for (int j = 0; j < tas->get_tiles_count(); j++) {
+ Vector2i tile_id = tas->get_tile_id(j);
+ for (int k = 0; k < tas->get_alternative_tiles_count(tile_id); k++) {
+ int alternative_id = tas->get_alternative_tile_id(tile_id, k);
+ TileData *tile_data = Object::cast_to<TileData>(tas->get_tile_data(tile_id, alternative_id));
+ ERR_FAIL_COND(!tile_data);
+
+ // Actually saving stuff.
+ if (p_array_prefix == "occlusion_layer_") {
+ for (int layer_index = begin; layer_index < end; layer_index++) {
+ ADD_UNDO(tile_data, vformat("occlusion_layer_%d/polygon", layer_index));
+ }
+ } else if (p_array_prefix == "physics_layer_") {
+ for (int layer_index = begin; layer_index < end; layer_index++) {
+ ADD_UNDO(tile_data, vformat("physics_layer_%d/polygons_count", layer_index));
+ for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(layer_index); polygon_index++) {
+ ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", layer_index, polygon_index));
+ ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, polygon_index));
+ ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, polygon_index));
+ }
+ }
+ } else if (p_array_prefix == "terrain_set_") {
+ ADD_UNDO(tile_data, "terrain_set");
+ for (int terrain_set_index = begin; terrain_set_index < end; terrain_set_index++) {
+ for (int l = 0; l < TileSet::CELL_NEIGHBOR_MAX; l++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(l);
+ if (tile_data->is_valid_peering_bit_terrain(bit)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[l]));
+ }
+ }
+ }
+ } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") {
+ for (int terrain_index = 0; terrain_index < TileSet::CELL_NEIGHBOR_MAX; terrain_index++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(terrain_index);
+ if (tile_data->is_valid_peering_bit_terrain(bit)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[terrain_index]));
+ }
+ }
+ } else if (p_array_prefix == "navigation_layer_") {
+ for (int layer_index = begin; layer_index < end; layer_index++) {
+ ADD_UNDO(tile_data, vformat("navigation_layer_%d/polygon", layer_index));
+ }
+ } else if (p_array_prefix == "custom_data_layer_") {
+ for (int layer_index = begin; layer_index < end; layer_index++) {
+ ADD_UNDO(tile_data, vformat("custom_data_%d", layer_index));
+ }
+ }
+ }
+ }
+ }
+ }
+#undef ADD_UNDO
+
+ // Add do method.
+ if (p_array_prefix == "occlusion_layer_") {
+ if (p_from_index < 0) {
+ undo_redo->add_do_method(tile_set, "add_occlusion_layer", p_to_pos);
+ } else if (p_to_pos < 0) {
+ undo_redo->add_do_method(tile_set, "remove_occlusion_layer", p_from_index);
+ } else {
+ undo_redo->add_do_method(tile_set, "move_occlusion_layer", p_from_index, p_to_pos);
+ }
+ } else if (p_array_prefix == "physics_layer_") {
+ if (p_from_index < 0) {
+ undo_redo->add_do_method(tile_set, "add_physics_layer", p_to_pos);
+ } else if (p_to_pos < 0) {
+ undo_redo->add_do_method(tile_set, "remove_physics_layer", p_from_index);
+ } else {
+ undo_redo->add_do_method(tile_set, "move_physics_layer", p_from_index, p_to_pos);
+ }
+ } else if (p_array_prefix == "terrain_set_") {
+ if (p_from_index < 0) {
+ undo_redo->add_do_method(tile_set, "add_terrain_set", p_to_pos);
+ } else if (p_to_pos < 0) {
+ undo_redo->add_do_method(tile_set, "remove_terrain_set", p_from_index);
+ } else {
+ undo_redo->add_do_method(tile_set, "move_terrain_set", p_from_index, p_to_pos);
+ }
+ } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") {
+ int terrain_set = components[0].trim_prefix("terrain_set_").to_int();
+ if (p_from_index < 0) {
+ undo_redo->add_do_method(tile_set, "add_terrain", terrain_set, p_to_pos);
+ } else if (p_to_pos < 0) {
+ undo_redo->add_do_method(tile_set, "remove_terrain", terrain_set, p_from_index);
+ } else {
+ undo_redo->add_do_method(tile_set, "move_terrain", terrain_set, p_from_index, p_to_pos);
+ }
+ } else if (p_array_prefix == "navigation_layer_") {
+ if (p_from_index < 0) {
+ undo_redo->add_do_method(tile_set, "add_navigation_layer", p_to_pos);
+ } else if (p_to_pos < 0) {
+ undo_redo->add_do_method(tile_set, "remove_navigation_layer", p_from_index);
+ } else {
+ undo_redo->add_do_method(tile_set, "move_navigation_layer", p_from_index, p_to_pos);
+ }
+ } else if (p_array_prefix == "custom_data_layer_") {
+ if (p_from_index < 0) {
+ undo_redo->add_do_method(tile_set, "add_custom_data_layer", p_to_pos);
+ } else if (p_to_pos < 0) {
+ undo_redo->add_do_method(tile_set, "remove_custom_data_layer", p_from_index);
+ } else {
+ undo_redo->add_do_method(tile_set, "move_custom_data_layer", p_from_index, p_to_pos);
+ }
+ }
+}
+
void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) {
UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo);
ERR_FAIL_COND(!undo_redo);
-#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, tile_data->get(property));
+#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property));
TileSet *tile_set = Object::cast_to<TileSet>(p_edited);
if (tile_set) {
Vector<String> components = p_property.split("/", true, 3);
@@ -350,30 +531,7 @@ void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p
TileData *tile_data = Object::cast_to<TileData>(tas->get_tile_data(tile_id, alternative_id));
ERR_FAIL_COND(!tile_data);
- if (p_property == "occlusion_layers_count") {
- int new_layer_count = p_new_value;
- int old_layer_count = tile_set->get_occlusion_layers_count();
- if (new_layer_count < old_layer_count) {
- for (int occclusion_layer_index = new_layer_count - 1; occclusion_layer_index < old_layer_count; occclusion_layer_index++) {
- ADD_UNDO(tile_data, vformat("occlusion_layer_%d/polygon", occclusion_layer_index));
- }
- }
- } else if (p_property == "physics_layers_count") {
- int new_layer_count = p_new_value;
- int old_layer_count = tile_set->get_physics_layers_count();
- if (new_layer_count < old_layer_count) {
- for (int physics_layer_index = new_layer_count - 1; physics_layer_index < old_layer_count; physics_layer_index++) {
- ADD_UNDO(tile_data, vformat("physics_layer_%d/polygons_count", physics_layer_index));
- for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(physics_layer_index); polygon_index++) {
- ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", physics_layer_index, polygon_index));
- ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", physics_layer_index, polygon_index));
- ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", physics_layer_index, polygon_index));
- }
- }
- }
- } else if ((p_property == "terrains_sets_count" && tile_data->get_terrain_set() >= (int)p_new_value) ||
- (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "mode") ||
- (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrains_count" && tile_data->get_terrain_set() == components[0].trim_prefix("terrain_set_").to_int() && (int)p_new_value < tile_set->get_terrains_count(tile_data->get_terrain_set()))) {
+ if (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "mode") {
ADD_UNDO(tile_data, "terrain_set");
for (int l = 0; l < TileSet::CELL_NEIGHBOR_MAX; l++) {
TileSet::CellNeighbor bit = TileSet::CellNeighbor(l);
@@ -381,22 +539,6 @@ void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p
ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[l]));
}
}
- } else if (p_property == "navigation_layers_count") {
- int new_layer_count = p_new_value;
- int old_layer_count = tile_set->get_navigation_layers_count();
- if (new_layer_count < old_layer_count) {
- for (int navigation_layer_index = new_layer_count - 1; navigation_layer_index < old_layer_count; navigation_layer_index++) {
- ADD_UNDO(tile_data, vformat("navigation_layer_%d/polygon", navigation_layer_index));
- }
- }
- } else if (p_property == "custom_data_layers_count") {
- int new_layer_count = p_new_value;
- int old_layer_count = tile_set->get_custom_data_layers_count();
- if (new_layer_count < old_layer_count) {
- for (int custom_data_layer_index = new_layer_count - 1; custom_data_layer_index < old_layer_count; custom_data_layer_index++) {
- ADD_UNDO(tile_data, vformat("custom_data_%d", custom_data_layer_index));
- }
- }
} else if (components.size() == 2 && components[0].begins_with("custom_data_layer_") && components[0].trim_prefix("custom_data_layer_").is_valid_int() && components[1] == "type") {
int custom_data_layer = components[0].trim_prefix("custom_data_layer_").is_valid_int();
ADD_UNDO(tile_data, vformat("custom_data_%d", custom_data_layer));
@@ -531,6 +673,7 @@ TileSetEditor::TileSetEditor() {
tile_set_scenes_collection_source_editor->hide();
// Registers UndoRedo inspector callback.
+ EditorNode::get_singleton()->get_editor_data().add_move_array_element_function(SNAME("TileSet"), callable_mp(this, &TileSetEditor::_move_tile_set_array_element));
EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetEditor::_undo_redo_inspector_callback));
}
diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h
index 970e3fabb6..fe854b2281 100644
--- a/editor/plugins/tiles/tile_set_editor.h
+++ b/editor/plugins/tiles/tile_set_editor.h
@@ -71,6 +71,7 @@ private:
void _tile_set_changed();
+ void _move_tile_set_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos);
void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value);
protected:
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index 729cc95bd7..e1552b3b60 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -314,16 +314,38 @@ int TileMap::get_quadrant_size() const {
return quadrant_size;
}
-void TileMap::set_layers_count(int p_layers_count) {
- ERR_FAIL_COND(p_layers_count < 0);
- _clear_internals();
+int TileMap::get_layers_count() const {
+ return layers.size();
+}
- layers.resize(p_layers_count);
+void TileMap::add_layer(int p_to_pos) {
+ if (p_to_pos < 0) {
+ p_to_pos = layers.size();
+ }
+
+ ERR_FAIL_INDEX(p_to_pos, (int)layers.size() + 1);
+
+ layers.insert(p_to_pos, TileMapLayer());
_recreate_internals();
notify_property_list_changed();
- if (selected_layer >= p_layers_count) {
- selected_layer = -1;
+ emit_signal(SNAME("changed"));
+
+ update_configuration_warnings();
+}
+
+void TileMap::move_layer(int p_layer, int p_to_pos) {
+ ERR_FAIL_INDEX(p_layer, (int)layers.size());
+ ERR_FAIL_INDEX(p_to_pos, (int)layers.size() + 1);
+
+ TileMapLayer tl = layers[p_layer];
+ layers.insert(p_to_pos, tl);
+ layers.remove(p_to_pos < p_layer ? p_layer + 1 : p_layer);
+ _recreate_internals();
+ notify_property_list_changed();
+
+ if (selected_layer == p_layer) {
+ selected_layer = p_to_pos < p_layer ? p_to_pos - 1 : p_to_pos;
}
emit_signal(SNAME("changed"));
@@ -331,8 +353,20 @@ void TileMap::set_layers_count(int p_layers_count) {
update_configuration_warnings();
}
-int TileMap::get_layers_count() const {
- return layers.size();
+void TileMap::remove_layer(int p_layer) {
+ ERR_FAIL_INDEX(p_layer, (int)layers.size());
+
+ layers.remove(p_layer);
+ _recreate_internals();
+ notify_property_list_changed();
+
+ if (selected_layer >= p_layer) {
+ selected_layer -= 1;
+ }
+
+ emit_signal(SNAME("changed"));
+
+ update_configuration_warnings();
}
void TileMap::set_layer_name(int p_layer, String p_name) {
@@ -2896,8 +2930,10 @@ void TileMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_quadrant_size", "size"), &TileMap::set_quadrant_size);
ClassDB::bind_method(D_METHOD("get_quadrant_size"), &TileMap::get_quadrant_size);
- ClassDB::bind_method(D_METHOD("set_layers_count", "layers_count"), &TileMap::set_layers_count);
ClassDB::bind_method(D_METHOD("get_layers_count"), &TileMap::get_layers_count);
+ ClassDB::bind_method(D_METHOD("add_layer"), &TileMap::add_layer);
+ ClassDB::bind_method(D_METHOD("move_layer"), &TileMap::move_layer);
+ ClassDB::bind_method(D_METHOD("remove_layer"), &TileMap::remove_layer);
ClassDB::bind_method(D_METHOD("set_layer_name", "layer", "name"), &TileMap::set_layer_name);
ClassDB::bind_method(D_METHOD("get_layer_name", "layer"), &TileMap::get_layer_name);
ClassDB::bind_method(D_METHOD("set_layer_enabled", "layer", "enabled"), &TileMap::set_layer_enabled);
@@ -2942,9 +2978,7 @@ void TileMap::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_collision_visibility_mode", "get_collision_visibility_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_navigation_visibility_mode", "get_navigation_visibility_mode");
- ADD_GROUP("Layers", "");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "layers_count"), "set_layers_count", "get_layers_count");
- ADD_PROPERTY_DEFAULT("layers_count", 1);
+ ADD_ARRAY("layers", "layer_");
ADD_PROPERTY_DEFAULT("format", FORMAT_1);
diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h
index 4e2d76a7b7..3ac50fc7cc 100644
--- a/scene/2d/tile_map.h
+++ b/scene/2d/tile_map.h
@@ -308,8 +308,10 @@ public:
static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0));
// Layers management.
- void set_layers_count(int p_layers_count);
int get_layers_count() const;
+ void add_layer(int p_to_pos);
+ void move_layer(int p_layer, int p_to_pos);
+ void remove_layer(int p_layer);
void set_layer_name(int p_layer, String p_name);
String get_layer_name(int p_layer) const;
void set_layer_enabled(int p_layer, bool p_visible);
diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp
index fcd31143a8..d383961d87 100644
--- a/scene/resources/tile_set.cpp
+++ b/scene/resources/tile_set.cpp
@@ -205,25 +205,46 @@ bool TileSet::is_uv_clipping() const {
return uv_clipping;
};
-void TileSet::set_occlusion_layers_count(int p_occlusion_layers_count) {
- ERR_FAIL_COND(p_occlusion_layers_count < 0);
- if (occlusion_layers.size() == p_occlusion_layers_count) {
- return;
- }
+int TileSet::get_occlusion_layers_count() const {
+ return occlusion_layers.size();
+};
- occlusion_layers.resize(p_occlusion_layers_count);
+void TileSet::add_occlusion_layer(int p_index) {
+ if (p_index < 0) {
+ p_index = occlusion_layers.size();
+ }
+ ERR_FAIL_INDEX(p_index, occlusion_layers.size() + 1);
+ occlusion_layers.insert(p_index, OcclusionLayer());
- for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) {
- E_source->get()->notify_tile_data_properties_should_change();
+ for (KeyValue<int, Ref<TileSetSource>> source : sources) {
+ source.value->add_occlusion_layer(p_index);
}
notify_property_list_changed();
emit_changed();
}
-int TileSet::get_occlusion_layers_count() const {
- return occlusion_layers.size();
-};
+void TileSet::move_occlusion_layer(int p_from_index, int p_to_pos) {
+ ERR_FAIL_INDEX(p_from_index, occlusion_layers.size());
+ ERR_FAIL_INDEX(p_to_pos, occlusion_layers.size() + 1);
+ occlusion_layers.insert(p_to_pos, occlusion_layers[p_from_index]);
+ occlusion_layers.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index);
+ for (KeyValue<int, Ref<TileSetSource>> source : sources) {
+ source.value->move_occlusion_layer(p_from_index, p_to_pos);
+ }
+ notify_property_list_changed();
+ emit_changed();
+}
+
+void TileSet::remove_occlusion_layer(int p_index) {
+ ERR_FAIL_INDEX(p_index, occlusion_layers.size());
+ occlusion_layers.remove(p_index);
+ for (KeyValue<int, Ref<TileSetSource>> source : sources) {
+ source.value->remove_occlusion_layer(p_index);
+ }
+ notify_property_list_changed();
+ emit_changed();
+}
void TileSet::set_occlusion_layer_light_mask(int p_layer_index, int p_light_mask) {
ERR_FAIL_INDEX(p_layer_index, occlusion_layers.size());
@@ -247,25 +268,45 @@ bool TileSet::get_occlusion_layer_sdf_collision(int p_layer_index) const {
return occlusion_layers[p_layer_index].sdf_collision;
}
-// Physics
-void TileSet::set_physics_layers_count(int p_physics_layers_count) {
- ERR_FAIL_COND(p_physics_layers_count < 0);
- if (physics_layers.size() == p_physics_layers_count) {
- return;
- }
+int TileSet::get_physics_layers_count() const {
+ return physics_layers.size();
+}
- physics_layers.resize(p_physics_layers_count);
+void TileSet::add_physics_layer(int p_index) {
+ if (p_index < 0) {
+ p_index = physics_layers.size();
+ }
+ ERR_FAIL_INDEX(p_index, physics_layers.size() + 1);
+ physics_layers.insert(p_index, PhysicsLayer());
- for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) {
- E_source->get()->notify_tile_data_properties_should_change();
+ for (KeyValue<int, Ref<TileSetSource>> source : sources) {
+ source.value->add_physics_layer(p_index);
}
notify_property_list_changed();
emit_changed();
}
-int TileSet::get_physics_layers_count() const {
- return physics_layers.size();
+void TileSet::move_physics_layer(int p_from_index, int p_to_pos) {
+ ERR_FAIL_INDEX(p_from_index, physics_layers.size());
+ ERR_FAIL_INDEX(p_to_pos, physics_layers.size() + 1);
+ physics_layers.insert(p_to_pos, physics_layers[p_from_index]);
+ physics_layers.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index);
+ for (KeyValue<int, Ref<TileSetSource>> source : sources) {
+ source.value->move_physics_layer(p_from_index, p_to_pos);
+ }
+ notify_property_list_changed();
+ emit_changed();
+}
+
+void TileSet::remove_physics_layer(int p_index) {
+ ERR_FAIL_INDEX(p_index, physics_layers.size());
+ physics_layers.remove(p_index);
+ for (KeyValue<int, Ref<TileSetSource>> source : sources) {
+ source.value->remove_physics_layer(p_index);
+ }
+ notify_property_list_changed();
+ emit_changed();
}
void TileSet::set_physics_layer_collision_layer(int p_layer_index, uint32_t p_layer) {
@@ -301,17 +342,45 @@ Ref<PhysicsMaterial> TileSet::get_physics_layer_physics_material(int p_layer_ind
}
// Terrains
-void TileSet::set_terrain_sets_count(int p_terrains_sets_count) {
- ERR_FAIL_COND(p_terrains_sets_count < 0);
+int TileSet::get_terrain_sets_count() const {
+ return terrain_sets.size();
+}
+
+void TileSet::add_terrain_set(int p_index) {
+ if (p_index < 0) {
+ p_index = terrain_sets.size();
+ }
+ ERR_FAIL_INDEX(p_index, terrain_sets.size() + 1);
+ terrain_sets.insert(p_index, TerrainSet());
- terrain_sets.resize(p_terrains_sets_count);
+ for (KeyValue<int, Ref<TileSetSource>> source : sources) {
+ source.value->add_terrain_set(p_index);
+ }
notify_property_list_changed();
emit_changed();
}
-int TileSet::get_terrain_sets_count() const {
- return terrain_sets.size();
+void TileSet::move_terrain_set(int p_from_index, int p_to_pos) {
+ ERR_FAIL_INDEX(p_from_index, terrain_sets.size());
+ ERR_FAIL_INDEX(p_to_pos, terrain_sets.size() + 1);
+ terrain_sets.insert(p_to_pos, terrain_sets[p_from_index]);
+ terrain_sets.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index);
+ for (KeyValue<int, Ref<TileSetSource>> source : sources) {
+ source.value->move_terrain_set(p_from_index, p_to_pos);
+ }
+ notify_property_list_changed();
+ emit_changed();
+}
+
+void TileSet::remove_terrain_set(int p_index) {
+ ERR_FAIL_INDEX(p_index, terrain_sets.size());
+ terrain_sets.remove(p_index);
+ for (KeyValue<int, Ref<TileSetSource>> source : sources) {
+ source.value->remove_terrain_set(p_index);
+ }
+ notify_property_list_changed();
+ emit_changed();
}
void TileSet::set_terrain_set_mode(int p_terrain_set, TerrainMode p_terrain_mode) {
@@ -330,36 +399,61 @@ TileSet::TerrainMode TileSet::get_terrain_set_mode(int p_terrain_set) const {
return terrain_sets[p_terrain_set].mode;
}
-void TileSet::set_terrains_count(int p_terrain_set, int p_terrains_layers_count) {
+int TileSet::get_terrains_count(int p_terrain_set) const {
+ ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), -1);
+ return terrain_sets[p_terrain_set].terrains.size();
+}
+
+void TileSet::add_terrain(int p_terrain_set, int p_index) {
ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size());
- ERR_FAIL_COND(p_terrains_layers_count < 0);
- if (terrain_sets[p_terrain_set].terrains.size() == p_terrains_layers_count) {
- return;
+ Vector<Terrain> &terrains = terrain_sets.write[p_terrain_set].terrains;
+ if (p_index < 0) {
+ p_index = terrains.size();
}
-
- int old_size = terrain_sets[p_terrain_set].terrains.size();
- terrain_sets.write[p_terrain_set].terrains.resize(p_terrains_layers_count);
+ ERR_FAIL_INDEX(p_index, terrains.size() + 1);
+ terrains.insert(p_index, Terrain());
// Default name and color
- for (int i = old_size; i < terrain_sets.write[p_terrain_set].terrains.size(); i++) {
- float hue_rotate = (i * 2 % 16) / 16.0;
- Color c;
- c.set_hsv(Math::fmod(float(hue_rotate), float(1.0)), 0.5, 0.5);
- terrain_sets.write[p_terrain_set].terrains.write[i].color = c;
- terrain_sets.write[p_terrain_set].terrains.write[i].name = String(vformat("Terrain %d", i));
+ float hue_rotate = (terrains.size() % 16) / 16.0;
+ Color c;
+ c.set_hsv(Math::fmod(float(hue_rotate), float(1.0)), 0.5, 0.5);
+ terrains.write[p_index].color = c;
+ terrains.write[p_index].name = String(vformat("Terrain %d", p_index));
+
+ for (KeyValue<int, Ref<TileSetSource>> source : sources) {
+ source.value->add_terrain(p_terrain_set, p_index);
}
- for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) {
- E_source->get()->notify_tile_data_properties_should_change();
- }
+ notify_property_list_changed();
+ emit_changed();
+}
+void TileSet::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) {
+ ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size());
+ Vector<Terrain> &terrains = terrain_sets.write[p_terrain_set].terrains;
+
+ ERR_FAIL_INDEX(p_from_index, terrains.size());
+ ERR_FAIL_INDEX(p_to_pos, terrains.size() + 1);
+ terrains.insert(p_to_pos, terrains[p_from_index]);
+ terrains.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index);
+ for (KeyValue<int, Ref<TileSetSource>> source : sources) {
+ source.value->move_terrain(p_terrain_set, p_from_index, p_to_pos);
+ }
notify_property_list_changed();
emit_changed();
}
-int TileSet::get_terrains_count(int p_terrain_set) const {
- ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), -1);
- return terrain_sets[p_terrain_set].terrains.size();
+void TileSet::remove_terrain(int p_terrain_set, int p_index) {
+ ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size());
+ Vector<Terrain> &terrains = terrain_sets.write[p_terrain_set].terrains;
+
+ ERR_FAIL_INDEX(p_index, terrains.size());
+ terrains.remove(p_index);
+ for (KeyValue<int, Ref<TileSetSource>> source : sources) {
+ source.value->remove_terrain(p_terrain_set, p_index);
+ }
+ notify_property_list_changed();
+ emit_changed();
}
void TileSet::set_terrain_name(int p_terrain_set, int p_terrain_index, String p_name) {
@@ -485,24 +579,45 @@ bool TileSet::is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeigh
}
// Navigation
-void TileSet::set_navigation_layers_count(int p_navigation_layers_count) {
- ERR_FAIL_COND(p_navigation_layers_count < 0);
- if (navigation_layers.size() == p_navigation_layers_count) {
- return;
- }
+int TileSet::get_navigation_layers_count() const {
+ return navigation_layers.size();
+}
- navigation_layers.resize(p_navigation_layers_count);
+void TileSet::add_navigation_layer(int p_index) {
+ if (p_index < 0) {
+ p_index = navigation_layers.size();
+ }
+ ERR_FAIL_INDEX(p_index, navigation_layers.size() + 1);
+ navigation_layers.insert(p_index, NavigationLayer());
- for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) {
- E_source->get()->notify_tile_data_properties_should_change();
+ for (KeyValue<int, Ref<TileSetSource>> source : sources) {
+ source.value->add_navigation_layer(p_index);
}
notify_property_list_changed();
emit_changed();
}
-int TileSet::get_navigation_layers_count() const {
- return navigation_layers.size();
+void TileSet::move_navigation_layer(int p_from_index, int p_to_pos) {
+ ERR_FAIL_INDEX(p_from_index, navigation_layers.size());
+ ERR_FAIL_INDEX(p_to_pos, navigation_layers.size() + 1);
+ navigation_layers.insert(p_to_pos, navigation_layers[p_from_index]);
+ navigation_layers.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index);
+ for (KeyValue<int, Ref<TileSetSource>> source : sources) {
+ source.value->move_navigation_layer(p_from_index, p_to_pos);
+ }
+ notify_property_list_changed();
+ emit_changed();
+}
+
+void TileSet::remove_navigation_layer(int p_index) {
+ ERR_FAIL_INDEX(p_index, navigation_layers.size());
+ navigation_layers.remove(p_index);
+ for (KeyValue<int, Ref<TileSetSource>> source : sources) {
+ source.value->remove_navigation_layer(p_index);
+ }
+ notify_property_list_changed();
+ emit_changed();
}
void TileSet::set_navigation_layer_layers(int p_layer_index, uint32_t p_layers) {
@@ -517,30 +632,52 @@ uint32_t TileSet::get_navigation_layer_layers(int p_layer_index) const {
}
// Custom data.
-void TileSet::set_custom_data_layers_count(int p_custom_data_layers_count) {
- ERR_FAIL_COND(p_custom_data_layers_count < 0);
- if (custom_data_layers.size() == p_custom_data_layers_count) {
- return;
- }
-
- custom_data_layers.resize(p_custom_data_layers_count);
+int TileSet::get_custom_data_layers_count() const {
+ return custom_data_layers.size();
+}
- for (Map<String, int>::Element *E = custom_data_layers_by_name.front(); E; E = E->next()) {
- if (E->get() >= custom_data_layers.size()) {
- custom_data_layers_by_name.erase(E);
- }
+void TileSet::add_custom_data_layer(int p_index) {
+ if (p_index < 0) {
+ p_index = custom_data_layers.size();
}
+ ERR_FAIL_INDEX(p_index, custom_data_layers.size() + 1);
+ custom_data_layers.insert(p_index, CustomDataLayer());
- for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) {
- E_source->get()->notify_tile_data_properties_should_change();
+ for (KeyValue<int, Ref<TileSetSource>> source : sources) {
+ source.value->add_custom_data_layer(p_index);
}
notify_property_list_changed();
emit_changed();
}
-int TileSet::get_custom_data_layers_count() const {
- return custom_data_layers.size();
+void TileSet::move_custom_data_layer(int p_from_index, int p_to_pos) {
+ ERR_FAIL_INDEX(p_from_index, custom_data_layers.size());
+ ERR_FAIL_INDEX(p_to_pos, custom_data_layers.size() + 1);
+ custom_data_layers.insert(p_to_pos, custom_data_layers[p_from_index]);
+ custom_data_layers.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index);
+ for (KeyValue<int, Ref<TileSetSource>> source : sources) {
+ source.value->move_custom_data_layer(p_from_index, p_to_pos);
+ }
+ notify_property_list_changed();
+ emit_changed();
+}
+
+void TileSet::remove_custom_data_layer(int p_index) {
+ ERR_FAIL_INDEX(p_index, custom_data_layers.size());
+ custom_data_layers.remove(p_index);
+ for (KeyValue<String, int> E : custom_data_layers_by_name) {
+ if (E.value == p_index) {
+ custom_data_layers_by_name.erase(E.key);
+ break;
+ }
+ }
+
+ for (KeyValue<int, Ref<TileSetSource>> source : sources) {
+ source.value->remove_custom_data_layer(p_index);
+ }
+ notify_property_list_changed();
+ emit_changed();
}
int TileSet::get_custom_data_layer_by_name(String p_value) const {
@@ -1110,7 +1247,11 @@ Vector<Vector<Ref<Texture2D>>> TileSet::generate_terrains_icons(Size2i p_size) {
if (is_valid_peering_bit_terrain(terrain_set, cell_neighbor)) {
int terrain = tile_data->get_peering_bit_terrain(cell_neighbor);
if (terrain >= 0) {
- bit_counts[terrain] += 1;
+ if (terrain >= (int)bit_counts.size()) {
+ WARN_PRINT(vformat("Invalid peering bit terrain: %d", terrain));
+ } else {
+ bit_counts[terrain] += 1;
+ }
}
}
}
@@ -1831,13 +1972,13 @@ void TileSet::_compatibility_conversion() {
if (ctd->occluder.is_valid()) {
if (get_occlusion_layers_count() < 1) {
- set_occlusion_layers_count(1);
+ add_occlusion_layer();
}
tile_data->set_occluder(0, ctd->occluder);
}
if (ctd->navigation.is_valid()) {
if (get_navigation_layers_count() < 1) {
- set_navigation_layers_count(1);
+ add_navigation_layer();
}
tile_data->set_navigation_polygon(0, ctd->autotile_navpoly_map[coords]);
}
@@ -1847,7 +1988,7 @@ void TileSet::_compatibility_conversion() {
// Add the shapes.
if (ctd->shapes.size() > 0) {
if (get_physics_layers_count() < 1) {
- set_physics_layers_count(1);
+ add_physics_layer();
}
}
for (int k = 0; k < ctd->shapes.size(); k++) {
@@ -1922,13 +2063,13 @@ void TileSet::_compatibility_conversion() {
tile_data->set_z_index(ctd->z_index);
if (ctd->autotile_occluder_map.has(coords)) {
if (get_occlusion_layers_count() < 1) {
- set_occlusion_layers_count(1);
+ add_occlusion_layer();
}
tile_data->set_occluder(0, ctd->autotile_occluder_map[coords]);
}
if (ctd->autotile_navpoly_map.has(coords)) {
if (get_navigation_layers_count() < 1) {
- set_navigation_layers_count(1);
+ add_navigation_layer();
}
tile_data->set_navigation_polygon(0, ctd->autotile_navpoly_map[coords]);
}
@@ -1942,7 +2083,7 @@ void TileSet::_compatibility_conversion() {
// Add the shapes.
if (ctd->shapes.size() > 0) {
if (get_physics_layers_count() < 1) {
- set_physics_layers_count(1);
+ add_physics_layer();
}
}
for (int k = 0; k < ctd->shapes.size(); k++) {
@@ -2206,15 +2347,15 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) {
ERR_FAIL_COND_V(index < 0, false);
if (components[1] == "light_mask") {
ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false);
- if (index >= occlusion_layers.size()) {
- set_occlusion_layers_count(index + 1);
+ while (index >= occlusion_layers.size()) {
+ add_occlusion_layer();
}
set_occlusion_layer_light_mask(index, p_value);
return true;
} else if (components[1] == "sdf_collision") {
ERR_FAIL_COND_V(p_value.get_type() != Variant::BOOL, false);
- if (index >= occlusion_layers.size()) {
- set_occlusion_layers_count(index + 1);
+ while (index >= occlusion_layers.size()) {
+ add_occlusion_layer();
}
set_occlusion_layer_sdf_collision(index, p_value);
return true;
@@ -2225,23 +2366,22 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) {
ERR_FAIL_COND_V(index < 0, false);
if (components[1] == "collision_layer") {
ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false);
- if (index >= physics_layers.size()) {
- set_physics_layers_count(index + 1);
+ while (index >= physics_layers.size()) {
+ add_physics_layer();
}
set_physics_layer_collision_layer(index, p_value);
return true;
} else if (components[1] == "collision_mask") {
ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false);
- if (index >= physics_layers.size()) {
- set_physics_layers_count(index + 1);
+ while (index >= physics_layers.size()) {
+ add_physics_layer();
}
set_physics_layer_collision_mask(index, p_value);
return true;
} else if (components[1] == "physics_material") {
Ref<PhysicsMaterial> physics_material = p_value;
- ERR_FAIL_COND_V(!physics_material.is_valid(), false);
- if (index >= physics_layers.size()) {
- set_physics_layers_count(index + 1);
+ while (index >= physics_layers.size()) {
+ add_physics_layer();
}
set_physics_layer_physics_material(index, physics_material);
return true;
@@ -2252,37 +2392,30 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) {
ERR_FAIL_COND_V(terrain_set_index < 0, false);
if (components[1] == "mode") {
ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false);
- if (terrain_set_index >= terrain_sets.size()) {
- set_terrain_sets_count(terrain_set_index + 1);
+ while (terrain_set_index >= terrain_sets.size()) {
+ add_terrain_set();
}
set_terrain_set_mode(terrain_set_index, TerrainMode(int(p_value)));
- } else if (components[1] == "terrains_count") {
- ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false);
- if (terrain_set_index >= terrain_sets.size()) {
- set_terrain_sets_count(terrain_set_index + 1);
- }
- set_terrains_count(terrain_set_index, p_value);
- return true;
} else if (components.size() >= 3 && components[1].begins_with("terrain_") && components[1].trim_prefix("terrain_").is_valid_int()) {
int terrain_index = components[1].trim_prefix("terrain_").to_int();
ERR_FAIL_COND_V(terrain_index < 0, false);
if (components[2] == "name") {
ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false);
- if (terrain_set_index >= terrain_sets.size()) {
- set_terrain_sets_count(terrain_set_index + 1);
+ while (terrain_set_index >= terrain_sets.size()) {
+ add_terrain_set();
}
- if (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) {
- set_terrains_count(terrain_set_index, terrain_index + 1);
+ while (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) {
+ add_terrain(terrain_set_index);
}
set_terrain_name(terrain_set_index, terrain_index, p_value);
return true;
} else if (components[2] == "color") {
ERR_FAIL_COND_V(p_value.get_type() != Variant::COLOR, false);
- if (terrain_set_index >= terrain_sets.size()) {
- set_terrain_sets_count(terrain_set_index + 1);
+ while (terrain_set_index >= terrain_sets.size()) {
+ add_terrain_set();
}
- if (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) {
- set_terrains_count(terrain_set_index, terrain_index + 1);
+ while (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) {
+ add_terrain(terrain_set_index);
}
set_terrain_color(terrain_set_index, terrain_index, p_value);
return true;
@@ -2294,8 +2427,8 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) {
ERR_FAIL_COND_V(index < 0, false);
if (components[1] == "layers") {
ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false);
- if (index >= navigation_layers.size()) {
- set_navigation_layers_count(index + 1);
+ while (index >= navigation_layers.size()) {
+ add_navigation_layer();
}
set_navigation_layer_layers(index, p_value);
return true;
@@ -2306,15 +2439,15 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) {
ERR_FAIL_COND_V(index < 0, false);
if (components[1] == "name") {
ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false);
- if (index >= custom_data_layers.size()) {
- set_custom_data_layers_count(index + 1);
+ while (index >= custom_data_layers.size()) {
+ add_custom_data_layer();
}
set_custom_data_name(index, p_value);
return true;
} else if (components[1] == "type") {
ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false);
- if (index >= custom_data_layers.size()) {
- set_custom_data_layers_count(index + 1);
+ while (index >= custom_data_layers.size()) {
+ add_custom_data_layer();
}
set_custom_data_type(index, Variant::Type(int(p_value)));
return true;
@@ -2402,9 +2535,6 @@ bool TileSet::_get(const StringName &p_name, Variant &r_ret) const {
if (components[1] == "mode") {
r_ret = get_terrain_set_mode(terrain_set_index);
return true;
- } else if (components[1] == "terrains_count") {
- r_ret = get_terrains_count(terrain_set_index);
- return true;
} else if (components.size() >= 3 && components[1].begins_with("terrain_") && components[1].trim_prefix("terrain_").is_valid_int()) {
int terrain_index = components[1].trim_prefix("terrain_").to_int();
if (terrain_index < 0 || terrain_index >= terrain_sets[terrain_set_index].terrains.size()) {
@@ -2522,7 +2652,7 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::NIL, "Terrains", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
for (int terrain_set_index = 0; terrain_set_index < terrain_sets.size(); terrain_set_index++) {
p_list->push_back(PropertyInfo(Variant::INT, vformat("terrain_set_%d/mode", terrain_set_index), PROPERTY_HINT_ENUM, "Match corners and sides,Match corners,Match sides"));
- p_list->push_back(PropertyInfo(Variant::INT, vformat("terrain_set_%d/terrains_count", terrain_set_index), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::NIL, vformat("terrain_set_%d/terrains", terrain_set_index), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, vformat("terrain_set_%d/terrain_", terrain_set_index)));
for (int terrain_index = 0; terrain_index < terrain_sets[terrain_set_index].terrains.size(); terrain_index++) {
p_list->push_back(PropertyInfo(Variant::STRING, vformat("terrain_set_%d/terrain_%d/name", terrain_set_index, terrain_index)));
p_list->push_back(PropertyInfo(Variant::COLOR, vformat("terrain_set_%d/terrain_%d/color", terrain_set_index, terrain_index)));
@@ -2590,16 +2720,20 @@ void TileSet::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_uv_clipping", "uv_clipping"), &TileSet::set_uv_clipping);
ClassDB::bind_method(D_METHOD("is_uv_clipping"), &TileSet::is_uv_clipping);
- ClassDB::bind_method(D_METHOD("set_occlusion_layers_count", "occlusion_layers_count"), &TileSet::set_occlusion_layers_count);
ClassDB::bind_method(D_METHOD("get_occlusion_layers_count"), &TileSet::get_occlusion_layers_count);
+ ClassDB::bind_method(D_METHOD("add_occlusion_layer", "to_position"), &TileSet::add_occlusion_layer, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("move_occlusion_layer", "layer_index", "to_position"), &TileSet::move_occlusion_layer);
+ ClassDB::bind_method(D_METHOD("remove_occlusion_layer", "layer_index"), &TileSet::remove_occlusion_layer);
ClassDB::bind_method(D_METHOD("set_occlusion_layer_light_mask", "layer_index", "light_mask"), &TileSet::set_occlusion_layer_light_mask);
ClassDB::bind_method(D_METHOD("get_occlusion_layer_light_mask"), &TileSet::get_occlusion_layer_light_mask);
ClassDB::bind_method(D_METHOD("set_occlusion_layer_sdf_collision", "layer_index", "sdf_collision"), &TileSet::set_occlusion_layer_sdf_collision);
ClassDB::bind_method(D_METHOD("get_occlusion_layer_sdf_collision"), &TileSet::get_occlusion_layer_sdf_collision);
// Physics
- ClassDB::bind_method(D_METHOD("set_physics_layers_count", "physics_layers_count"), &TileSet::set_physics_layers_count);
ClassDB::bind_method(D_METHOD("get_physics_layers_count"), &TileSet::get_physics_layers_count);
+ ClassDB::bind_method(D_METHOD("add_physics_layer", "to_position"), &TileSet::add_physics_layer, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("move_physics_layer", "layer_index", "to_position"), &TileSet::move_physics_layer);
+ ClassDB::bind_method(D_METHOD("remove_physics_layer", "layer_index"), &TileSet::remove_physics_layer);
ClassDB::bind_method(D_METHOD("set_physics_layer_collision_layer", "layer_index", "layer"), &TileSet::set_physics_layer_collision_layer);
ClassDB::bind_method(D_METHOD("get_physics_layer_collision_layer", "layer_index"), &TileSet::get_physics_layer_collision_layer);
ClassDB::bind_method(D_METHOD("set_physics_layer_collision_mask", "layer_index", "mask"), &TileSet::set_physics_layer_collision_mask);
@@ -2608,27 +2742,35 @@ void TileSet::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_physics_layer_physics_material", "layer_index"), &TileSet::get_physics_layer_physics_material);
// Terrains
- ClassDB::bind_method(D_METHOD("set_terrain_sets_count", "terrain_sets_count"), &TileSet::set_terrain_sets_count);
ClassDB::bind_method(D_METHOD("get_terrain_sets_count"), &TileSet::get_terrain_sets_count);
+ ClassDB::bind_method(D_METHOD("add_terrain_set", "to_position"), &TileSet::add_terrain_set, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("move_terrain_set", "layer_index", "to_position"), &TileSet::move_terrain_set);
+ ClassDB::bind_method(D_METHOD("remove_terrain_set", "layer_index"), &TileSet::remove_terrain_set);
ClassDB::bind_method(D_METHOD("set_terrain_set_mode", "terrain_set", "mode"), &TileSet::set_terrain_set_mode);
ClassDB::bind_method(D_METHOD("get_terrain_set_mode", "terrain_set"), &TileSet::get_terrain_set_mode);
- ClassDB::bind_method(D_METHOD("set_terrains_count", "terrain_set", "terrains_count"), &TileSet::set_terrains_count);
ClassDB::bind_method(D_METHOD("get_terrains_count", "terrain_set"), &TileSet::get_terrains_count);
+ ClassDB::bind_method(D_METHOD("add_terrain", "terrain_set", "to_position"), &TileSet::add_terrain, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("move_terrain", "terrain_set", "terrain_index", "to_position"), &TileSet::move_terrain);
+ ClassDB::bind_method(D_METHOD("remove_terrain", "terrain_set", "terrain_index"), &TileSet::remove_terrain);
ClassDB::bind_method(D_METHOD("set_terrain_name", "terrain_set", "terrain_index", "name"), &TileSet::set_terrain_name);
ClassDB::bind_method(D_METHOD("get_terrain_name", "terrain_set", "terrain_index"), &TileSet::get_terrain_name);
ClassDB::bind_method(D_METHOD("set_terrain_color", "terrain_set", "terrain_index", "color"), &TileSet::set_terrain_color);
ClassDB::bind_method(D_METHOD("get_terrain_color", "terrain_set", "terrain_index"), &TileSet::get_terrain_color);
// Navigation
- ClassDB::bind_method(D_METHOD("set_navigation_layers_count", "navigation_layers_count"), &TileSet::set_navigation_layers_count);
ClassDB::bind_method(D_METHOD("get_navigation_layers_count"), &TileSet::get_navigation_layers_count);
+ ClassDB::bind_method(D_METHOD("add_navigation_layer", "to_position"), &TileSet::add_navigation_layer, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("move_navigation_layer", "layer_index", "to_position"), &TileSet::move_navigation_layer);
+ ClassDB::bind_method(D_METHOD("remove_navigation_layer", "layer_index"), &TileSet::remove_navigation_layer);
ClassDB::bind_method(D_METHOD("set_navigation_layer_layers", "layer_index", "layers"), &TileSet::set_navigation_layer_layers);
ClassDB::bind_method(D_METHOD("get_navigation_layer_layers", "layer_index"), &TileSet::get_navigation_layer_layers);
// Custom data
- ClassDB::bind_method(D_METHOD("set_custom_data_layers_count", "custom_data_layers_count"), &TileSet::set_custom_data_layers_count);
ClassDB::bind_method(D_METHOD("get_custom_data_layers_count"), &TileSet::get_custom_data_layers_count);
+ ClassDB::bind_method(D_METHOD("add_custom_data_layer", "to_position"), &TileSet::add_custom_data_layer, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("move_custom_data_layer", "layer_index", "to_position"), &TileSet::move_custom_data_layer);
+ ClassDB::bind_method(D_METHOD("remove_custom_data_layer", "layer_index"), &TileSet::remove_custom_data_layer);
// Tile proxies
ClassDB::bind_method(D_METHOD("set_source_level_tile_proxy", "source_from", "source_to"), &TileSet::set_source_level_tile_proxy);
@@ -2653,19 +2795,19 @@ void TileSet::_bind_methods() {
ADD_GROUP("Rendering", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uv_clipping"), "set_uv_clipping", "is_uv_clipping");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "occlusion_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_occlusion_layers_count", "get_occlusion_layers_count");
+ ADD_ARRAY("occlusion_layers", "occlusion_layer_");
ADD_GROUP("Physics", "");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_physics_layers_count", "get_physics_layers_count");
+ ADD_ARRAY("physics_layers", "physics_layer_");
ADD_GROUP("Terrains", "");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "terrains_sets_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_terrain_sets_count", "get_terrain_sets_count");
+ ADD_ARRAY("terrain_sets", "terrain_set_");
ADD_GROUP("Navigation", "");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_navigation_layers_count", "get_navigation_layers_count");
+ ADD_ARRAY("navigation_layers", "navigation_layer_");
ADD_GROUP("Custom data", "");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "custom_data_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_custom_data_layers_count", "get_custom_data_layers_count");
+ ADD_ARRAY("custom_data_layers", "custom_data_layer_");
// -- Enum binding --
BIND_ENUM_CONSTANT(TILE_SHAPE_SQUARE);
@@ -2750,6 +2892,150 @@ void TileSetAtlasSource::notify_tile_data_properties_should_change() {
}
}
+void TileSetAtlasSource::add_occlusion_layer(int p_to_pos) {
+ for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) {
+ for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) {
+ E_alternative.value->add_occlusion_layer(p_to_pos);
+ }
+ }
+}
+
+void TileSetAtlasSource::move_occlusion_layer(int p_from_index, int p_to_pos) {
+ for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) {
+ for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) {
+ E_alternative.value->move_occlusion_layer(p_from_index, p_to_pos);
+ }
+ }
+}
+
+void TileSetAtlasSource::remove_occlusion_layer(int p_index) {
+ for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) {
+ for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) {
+ E_alternative.value->remove_occlusion_layer(p_index);
+ }
+ }
+}
+
+void TileSetAtlasSource::add_physics_layer(int p_to_pos) {
+ for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) {
+ for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) {
+ E_alternative.value->add_physics_layer(p_to_pos);
+ }
+ }
+}
+
+void TileSetAtlasSource::move_physics_layer(int p_from_index, int p_to_pos) {
+ for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) {
+ for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) {
+ E_alternative.value->move_physics_layer(p_from_index, p_to_pos);
+ }
+ }
+}
+
+void TileSetAtlasSource::remove_physics_layer(int p_index) {
+ for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) {
+ for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) {
+ E_alternative.value->remove_physics_layer(p_index);
+ }
+ }
+}
+
+void TileSetAtlasSource::add_terrain_set(int p_to_pos) {
+ for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) {
+ for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) {
+ E_alternative.value->add_terrain_set(p_to_pos);
+ }
+ }
+}
+
+void TileSetAtlasSource::move_terrain_set(int p_from_index, int p_to_pos) {
+ for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) {
+ for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) {
+ E_alternative.value->move_terrain_set(p_from_index, p_to_pos);
+ }
+ }
+}
+
+void TileSetAtlasSource::remove_terrain_set(int p_index) {
+ for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) {
+ for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) {
+ E_alternative.value->remove_terrain_set(p_index);
+ }
+ }
+}
+
+void TileSetAtlasSource::add_terrain(int p_terrain_set, int p_to_pos) {
+ for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) {
+ for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) {
+ E_alternative.value->add_terrain(p_terrain_set, p_to_pos);
+ }
+ }
+}
+
+void TileSetAtlasSource::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) {
+ for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) {
+ for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) {
+ E_alternative.value->move_terrain(p_terrain_set, p_from_index, p_to_pos);
+ }
+ }
+}
+
+void TileSetAtlasSource::remove_terrain(int p_terrain_set, int p_index) {
+ for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) {
+ for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) {
+ E_alternative.value->remove_terrain(p_terrain_set, p_index);
+ }
+ }
+}
+
+void TileSetAtlasSource::add_navigation_layer(int p_to_pos) {
+ for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) {
+ for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) {
+ E_alternative.value->add_navigation_layer(p_to_pos);
+ }
+ }
+}
+
+void TileSetAtlasSource::move_navigation_layer(int p_from_index, int p_to_pos) {
+ for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) {
+ for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) {
+ E_alternative.value->move_navigation_layer(p_from_index, p_to_pos);
+ }
+ }
+}
+
+void TileSetAtlasSource::remove_navigation_layer(int p_index) {
+ for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) {
+ for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) {
+ E_alternative.value->remove_navigation_layer(p_index);
+ }
+ }
+}
+
+void TileSetAtlasSource::add_custom_data_layer(int p_to_pos) {
+ for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) {
+ for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) {
+ E_alternative.value->add_custom_data_layer(p_to_pos);
+ }
+ }
+}
+
+void TileSetAtlasSource::move_custom_data_layer(int p_from_index, int p_to_pos) {
+ for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) {
+ for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) {
+ E_alternative.value->move_custom_data_layer(p_from_index, p_to_pos);
+ }
+ }
+}
+
+void TileSetAtlasSource::remove_custom_data_layer(int p_index) {
+ for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) {
+ for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) {
+ E_alternative.value->remove_custom_data_layer(p_index);
+ }
+ }
+}
+
void TileSetAtlasSource::reset_state() {
// Reset all TileData.
for (Map<Vector2i, TileAlternativesData>::Element *E_tile = tiles.front(); E_tile; E_tile = E_tile->next()) {
@@ -3575,6 +3861,155 @@ void TileData::notify_tile_data_properties_should_change() {
emit_signal(SNAME("changed"));
}
+void TileData::add_occlusion_layer(int p_to_pos) {
+ if (p_to_pos < 0) {
+ p_to_pos = occluders.size();
+ }
+ ERR_FAIL_INDEX(p_to_pos, occluders.size() + 1);
+ occluders.insert(p_to_pos, Ref<OccluderPolygon2D>());
+}
+
+void TileData::move_occlusion_layer(int p_from_index, int p_to_pos) {
+ ERR_FAIL_INDEX(p_from_index, occluders.size());
+ ERR_FAIL_INDEX(p_to_pos, occluders.size() + 1);
+ occluders.insert(p_to_pos, occluders[p_from_index]);
+ occluders.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index);
+}
+
+void TileData::remove_occlusion_layer(int p_index) {
+ ERR_FAIL_INDEX(p_index, occluders.size());
+ occluders.remove(p_index);
+}
+
+void TileData::add_physics_layer(int p_to_pos) {
+ if (p_to_pos < 0) {
+ p_to_pos = physics.size();
+ }
+ ERR_FAIL_INDEX(p_to_pos, physics.size() + 1);
+ physics.insert(p_to_pos, PhysicsLayerTileData());
+}
+
+void TileData::move_physics_layer(int p_from_index, int p_to_pos) {
+ ERR_FAIL_INDEX(p_from_index, physics.size());
+ ERR_FAIL_INDEX(p_to_pos, physics.size() + 1);
+ physics.insert(p_to_pos, physics[p_from_index]);
+ physics.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index);
+}
+
+void TileData::remove_physics_layer(int p_index) {
+ ERR_FAIL_INDEX(p_index, physics.size());
+ physics.remove(p_index);
+}
+
+void TileData::add_terrain_set(int p_to_pos) {
+ if (p_to_pos >= 0 && p_to_pos <= terrain_set) {
+ terrain_set += 1;
+ }
+}
+
+void TileData::move_terrain_set(int p_from_index, int p_to_pos) {
+ if (p_from_index == terrain_set) {
+ terrain_set = (p_from_index < p_to_pos) ? p_to_pos - 1 : p_to_pos;
+ } else {
+ if (p_from_index < terrain_set) {
+ terrain_set -= 1;
+ }
+ if (p_to_pos <= terrain_set) {
+ terrain_set += 1;
+ }
+ }
+}
+
+void TileData::remove_terrain_set(int p_index) {
+ if (p_index == terrain_set) {
+ terrain_set = -1;
+ for (int i = 0; i < 16; i++) {
+ terrain_peering_bits[i] = -1;
+ }
+ } else if (terrain_set > p_index) {
+ terrain_set -= 1;
+ }
+}
+
+void TileData::add_terrain(int p_terrain_set, int p_to_pos) {
+ if (terrain_set == p_terrain_set) {
+ for (int i = 0; i < 16; i++) {
+ if (p_to_pos >= 0 && p_to_pos <= terrain_peering_bits[i]) {
+ terrain_peering_bits[i] += 1;
+ }
+ }
+ }
+}
+
+void TileData::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) {
+ if (terrain_set == p_terrain_set) {
+ for (int i = 0; i < 16; i++) {
+ if (p_from_index == terrain_peering_bits[i]) {
+ terrain_peering_bits[i] = (p_from_index < p_to_pos) ? p_to_pos - 1 : p_to_pos;
+ } else {
+ if (p_from_index < terrain_peering_bits[i]) {
+ terrain_peering_bits[i] -= 1;
+ }
+ if (p_to_pos <= terrain_peering_bits[i]) {
+ terrain_peering_bits[i] += 1;
+ }
+ }
+ }
+ }
+}
+
+void TileData::remove_terrain(int p_terrain_set, int p_index) {
+ if (terrain_set == p_terrain_set) {
+ for (int i = 0; i < 16; i++) {
+ if (terrain_peering_bits[i] == p_index) {
+ terrain_peering_bits[i] = -1;
+ } else if (terrain_peering_bits[i] > p_index) {
+ terrain_peering_bits[i] -= 1;
+ }
+ }
+ }
+}
+
+void TileData::add_navigation_layer(int p_to_pos) {
+ if (p_to_pos < 0) {
+ p_to_pos = navigation.size();
+ }
+ ERR_FAIL_INDEX(p_to_pos, navigation.size() + 1);
+ navigation.insert(p_to_pos, Ref<NavigationPolygon>());
+}
+
+void TileData::move_navigation_layer(int p_from_index, int p_to_pos) {
+ ERR_FAIL_INDEX(p_from_index, navigation.size());
+ ERR_FAIL_INDEX(p_to_pos, navigation.size() + 1);
+ navigation.insert(p_to_pos, navigation[p_from_index]);
+ navigation.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index);
+}
+
+void TileData::remove_navigation_layer(int p_index) {
+ ERR_FAIL_INDEX(p_index, navigation.size());
+ navigation.remove(p_index);
+}
+
+void TileData::add_custom_data_layer(int p_to_pos) {
+ if (p_to_pos < 0) {
+ p_to_pos = custom_data.size();
+ }
+ ERR_FAIL_INDEX(p_to_pos, custom_data.size() + 1);
+ custom_data.insert(p_to_pos, Variant());
+}
+
+void TileData::move_custom_data_layer(int p_from_index, int p_to_pos) {
+ ERR_FAIL_INDEX(p_from_index, custom_data.size());
+ ERR_FAIL_INDEX(p_to_pos, custom_data.size() + 1);
+ custom_data.insert(p_to_pos, navigation[p_from_index]);
+ custom_data.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index);
+}
+
+void TileData::remove_custom_data_layer(int p_index) {
+ ERR_FAIL_INDEX(p_index, custom_data.size());
+ custom_data.remove(p_index);
+}
+
void TileData::reset_state() {
occluders.clear();
physics.clear();
diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h
index 35e6999d13..29a71d31d2 100644
--- a/scene/resources/tile_set.h
+++ b/scene/resources/tile_set.h
@@ -225,10 +225,10 @@ private:
bool terrain_bits_meshes_dirty = true;
// Navigation
- struct Navigationlayer {
+ struct NavigationLayer {
uint32_t layers = 1;
};
- Vector<Navigationlayer> navigation_layers;
+ Vector<NavigationLayer> navigation_layers;
// CustomData
struct CustomDataLayer {
@@ -298,16 +298,20 @@ public:
void set_uv_clipping(bool p_uv_clipping);
bool is_uv_clipping() const;
- void set_occlusion_layers_count(int p_occlusion_layers_count);
int get_occlusion_layers_count() const;
+ void add_occlusion_layer(int p_index = -1);
+ void move_occlusion_layer(int p_from_index, int p_to_pos);
+ void remove_occlusion_layer(int p_index);
void set_occlusion_layer_light_mask(int p_layer_index, int p_light_mask);
int get_occlusion_layer_light_mask(int p_layer_index) const;
void set_occlusion_layer_sdf_collision(int p_layer_index, int p_sdf_collision);
bool get_occlusion_layer_sdf_collision(int p_layer_index) const;
// Physics
- void set_physics_layers_count(int p_physics_layers_count);
int get_physics_layers_count() const;
+ void add_physics_layer(int p_index = -1);
+ void move_physics_layer(int p_from_index, int p_to_pos);
+ void remove_physics_layer(int p_index);
void set_physics_layer_collision_layer(int p_layer_index, uint32_t p_layer);
uint32_t get_physics_layer_collision_layer(int p_layer_index) const;
void set_physics_layer_collision_mask(int p_layer_index, uint32_t p_mask);
@@ -315,13 +319,19 @@ public:
void set_physics_layer_physics_material(int p_layer_index, Ref<PhysicsMaterial> p_physics_material);
Ref<PhysicsMaterial> get_physics_layer_physics_material(int p_layer_index) const;
- // Terrains
- void set_terrain_sets_count(int p_terrains_sets_count);
+ // Terrain sets
int get_terrain_sets_count() const;
+ void add_terrain_set(int p_index = -1);
+ void move_terrain_set(int p_from_index, int p_to_pos);
+ void remove_terrain_set(int p_index);
void set_terrain_set_mode(int p_terrain_set, TerrainMode p_terrain_mode);
TerrainMode get_terrain_set_mode(int p_terrain_set) const;
- void set_terrains_count(int p_terrain_set, int p_terrains_count);
+
+ // Terrains
int get_terrains_count(int p_terrain_set) const;
+ void add_terrain(int p_terrain_set, int p_index = -1);
+ void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos);
+ void remove_terrain(int p_terrain_set, int p_index);
void set_terrain_name(int p_terrain_set, int p_terrain_index, String p_name);
String get_terrain_name(int p_terrain_set, int p_terrain_index) const;
void set_terrain_color(int p_terrain_set, int p_terrain_index, Color p_color);
@@ -330,14 +340,18 @@ public:
bool is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeighbor p_peering_bit) const;
// Navigation
- void set_navigation_layers_count(int p_navigation_layers_count);
int get_navigation_layers_count() const;
+ void add_navigation_layer(int p_index = -1);
+ void move_navigation_layer(int p_from_index, int p_to_pos);
+ void remove_navigation_layer(int p_index);
void set_navigation_layer_layers(int p_layer_index, uint32_t p_layers);
uint32_t get_navigation_layer_layers(int p_layer_index) const;
// Custom data
- void set_custom_data_layers_count(int p_custom_data_layers_count);
int get_custom_data_layers_count() const;
+ void add_custom_data_layer(int p_index = -1);
+ void move_custom_data_layer(int p_from_index, int p_to_pos);
+ void remove_custom_data_layer(int p_index);
int get_custom_data_layer_by_name(String p_value) const;
void set_custom_data_name(int p_layer_id, String p_value);
String get_custom_data_name(int p_layer_id) const;
@@ -397,6 +411,24 @@ public:
// Not exposed.
virtual void set_tile_set(const TileSet *p_tile_set);
virtual void notify_tile_data_properties_should_change(){};
+ virtual void add_occlusion_layer(int p_index){};
+ virtual void move_occlusion_layer(int p_from_index, int p_to_pos){};
+ virtual void remove_occlusion_layer(int p_index){};
+ virtual void add_physics_layer(int p_index){};
+ virtual void move_physics_layer(int p_from_index, int p_to_pos){};
+ virtual void remove_physics_layer(int p_index){};
+ virtual void add_terrain_set(int p_index){};
+ virtual void move_terrain_set(int p_from_index, int p_to_pos){};
+ virtual void remove_terrain_set(int p_index){};
+ virtual void add_terrain(int p_terrain_set, int p_index){};
+ virtual void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos){};
+ virtual void remove_terrain(int p_terrain_set, int p_index){};
+ virtual void add_navigation_layer(int p_index){};
+ virtual void move_navigation_layer(int p_from_index, int p_to_pos){};
+ virtual void remove_navigation_layer(int p_index){};
+ virtual void add_custom_data_layer(int p_index){};
+ virtual void move_custom_data_layer(int p_from_index, int p_to_pos){};
+ virtual void remove_custom_data_layer(int p_index){};
virtual void reset_state() override{};
// Tiles.
@@ -448,6 +480,24 @@ public:
// Not exposed.
virtual void set_tile_set(const TileSet *p_tile_set) override;
virtual void notify_tile_data_properties_should_change() override;
+ virtual void add_occlusion_layer(int p_index) override;
+ virtual void move_occlusion_layer(int p_from_index, int p_to_pos) override;
+ virtual void remove_occlusion_layer(int p_index) override;
+ virtual void add_physics_layer(int p_index) override;
+ virtual void move_physics_layer(int p_from_index, int p_to_pos) override;
+ virtual void remove_physics_layer(int p_index) override;
+ virtual void add_terrain_set(int p_index) override;
+ virtual void move_terrain_set(int p_from_index, int p_to_pos) override;
+ virtual void remove_terrain_set(int p_index) override;
+ virtual void add_terrain(int p_terrain_set, int p_index) override;
+ virtual void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) override;
+ virtual void remove_terrain(int p_terrain_set, int p_index) override;
+ virtual void add_navigation_layer(int p_index) override;
+ virtual void move_navigation_layer(int p_from_index, int p_to_pos) override;
+ virtual void remove_navigation_layer(int p_index) override;
+ virtual void add_custom_data_layer(int p_index) override;
+ virtual void move_custom_data_layer(int p_from_index, int p_to_pos) override;
+ virtual void remove_custom_data_layer(int p_index) override;
virtual void reset_state() override;
// Base properties.
@@ -528,7 +578,7 @@ public:
int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const override;
bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const override;
- // Scenes sccessors. Lot are similar to "Alternative tiles".
+ // Scenes accessors. Lot are similar to "Alternative tiles".
int get_scene_tiles_count() { return get_alternative_tiles_count(Vector2i()); }
int get_scene_tile_id(int p_index) { return get_alternative_tile_id(Vector2i(), p_index); };
bool has_scene_tile_id(int p_id) { return has_alternative_tile(Vector2i(), p_id); };
@@ -597,6 +647,24 @@ public:
// Not exposed.
void set_tile_set(const TileSet *p_tile_set);
void notify_tile_data_properties_should_change();
+ void add_occlusion_layer(int p_index);
+ void move_occlusion_layer(int p_from_index, int p_to_pos);
+ void remove_occlusion_layer(int p_index);
+ void add_physics_layer(int p_index);
+ void move_physics_layer(int p_from_index, int p_to_pos);
+ void remove_physics_layer(int p_index);
+ void add_terrain_set(int p_index);
+ void move_terrain_set(int p_from_index, int p_to_pos);
+ void remove_terrain_set(int p_index);
+ void add_terrain(int p_terrain_set, int p_index);
+ void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos);
+ void remove_terrain(int p_terrain_set, int p_index);
+ void add_navigation_layer(int p_index);
+ void move_navigation_layer(int p_from_index, int p_to_pos);
+ void remove_navigation_layer(int p_index);
+ void add_custom_data_layer(int p_index);
+ void move_custom_data_layer(int p_from_index, int p_to_pos);
+ void remove_custom_data_layer(int p_index);
void reset_state();
void set_allow_transform(bool p_allow_transform);
bool is_allowing_transform() const;
diff --git a/tests/test_class_db.h b/tests/test_class_db.h
index ea680da5d6..20397bb144 100644
--- a/tests/test_class_db.h
+++ b/tests/test_class_db.h
@@ -527,7 +527,7 @@ void add_exposed_classes(Context &r_context) {
Map<StringName, StringName> accessor_methods;
for (const PropertyInfo &property : property_list) {
- if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY) {
+ if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY || (property.type == Variant::NIL && property.usage & PROPERTY_USAGE_ARRAY)) {
continue;
}