summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGilles Roudière <gilles.roudiere@gmail.com>2021-09-29 17:48:27 +0200
committerGilles Roudière <gilles.roudiere@gmail.com>2021-10-19 11:57:37 +0200
commit1a95f893c4ed4d4ac872a294c64d06ee70fa10bd (patch)
treefb0deddf534c76189f0044bca36b46ef2f8dc444
parent4387f9645b1f54755506804770ba15c6c9cd5094 (diff)
Implement TileMap patterns palette
-rw-r--r--doc/classes/Control.xml6
-rw-r--r--doc/classes/TileMap.xml26
-rw-r--r--doc/classes/TileMapPattern.xml85
-rw-r--r--doc/classes/TileSet.xml28
-rw-r--r--editor/plugins/editor_preview_plugins.h18
-rw-r--r--editor/plugins/tiles/tile_map_editor.cpp577
-rw-r--r--editor/plugins/tiles/tile_map_editor.h59
-rw-r--r--editor/plugins/tiles/tile_set_editor.cpp88
-rw-r--r--editor/plugins/tiles/tile_set_editor.h17
-rw-r--r--editor/plugins/tiles/tiles_editor_plugin.cpp125
-rw-r--r--editor/plugins/tiles/tiles_editor_plugin.h21
-rw-r--r--scene/2d/tile_map.cpp107
-rw-r--r--scene/2d/tile_map.h77
-rw-r--r--scene/gui/control.cpp2
-rw-r--r--scene/gui/control.h2
-rw-r--r--scene/register_scene_types.cpp1
-rw-r--r--scene/resources/tile_set.cpp238
-rw-r--r--scene/resources/tile_set.h86
18 files changed, 1163 insertions, 400 deletions
diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml
index 7c8239977f..332d171457 100644
--- a/doc/classes/Control.xml
+++ b/doc/classes/Control.xml
@@ -763,10 +763,10 @@
</method>
<method name="set_drag_forwarding">
<return type="void" />
- <argument index="0" name="target" type="Node" />
+ <argument index="0" name="target" type="Object" />
<description>
- Forwards the handling of this control's drag and drop to [code]target[/code] node.
- Forwarding can be implemented in the target node similar to the methods [method _get_drag_data], [method _can_drop_data], and [method _drop_data] but with two differences:
+ Forwards the handling of this control's drag and drop to [code]target[/code] object.
+ Forwarding can be implemented in the target object similar to the methods [method _get_drag_data], [method _can_drop_data], and [method _drop_data] but with two differences:
1. The function name must be suffixed with [b]_fw[/b]
2. The function must take an extra argument that is the control doing the forwarding
[codeblocks]
diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml
index d7108c3a2a..e65d5b4533 100644
--- a/doc/classes/TileMap.xml
+++ b/doc/classes/TileMap.xml
@@ -117,6 +117,14 @@
Returns the neighboring cell to the one at coordinates [code]coords[/code], indentified by the [code]neighbor[/code] direction. This method takes into account the different layouts a TileMap can take.
</description>
</method>
+ <method name="get_pattern">
+ <return type="TileMapPattern" />
+ <argument index="0" name="layer" type="int" />
+ <argument index="1" name="coords_array" type="Vector2i[]" />
+ <description>
+ Creates a new [TileMapPattern] from the given layer and set of cells.
+ </description>
+ </method>
<method name="get_surrounding_tiles">
<return type="Vector2i[]" />
<argument index="0" name="coords" type="Vector2i" />
@@ -151,6 +159,15 @@
Returns if a layer Y-sorts its tiles.
</description>
</method>
+ <method name="map_pattern">
+ <return type="Vector2i" />
+ <argument index="0" name="position_in_tilemap" type="Vector2i" />
+ <argument index="1" name="coords_in_pattern" type="Vector2i" />
+ <argument index="2" name="pattern" type="TileMapPattern" />
+ <description>
+ Returns for the given coodinate [code]coords_in_pattern[/code] in a [TileMapPattern] the corresponding cell coordinates if the pattern was pasted at the [code]position_in_tilemap[/code] coordinates (see [method set_pattern]). This mapping is required as in half-offset tile shapes, the mapping might not work by calculating [code]position_in_tile_map + coords_in_pattern[/code]
+ </description>
+ </method>
<method name="map_to_world" qualifiers="const">
<return type="Vector2" />
<argument index="0" name="map_position" type="Vector2i" />
@@ -237,6 +254,15 @@
Sets a layers Z-index value. This Z-index is added to each tile's Z-index value.
</description>
</method>
+ <method name="set_pattern">
+ <return type="void" />
+ <argument index="0" name="layer" type="int" />
+ <argument index="1" name="position" type="Vector2i" />
+ <argument index="2" name="pattern" type="TileMapPattern" />
+ <description>
+ Paste the given [TileMapPattern] at the given [code]position[/code] and [code]layer[/code] in the tile map.
+ </description>
+ </method>
<method name="world_to_map" qualifiers="const">
<return type="Vector2i" />
<argument index="0" name="world_position" type="Vector2" />
diff --git a/doc/classes/TileMapPattern.xml b/doc/classes/TileMapPattern.xml
new file mode 100644
index 0000000000..4c46625423
--- /dev/null
+++ b/doc/classes/TileMapPattern.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="TileMapPattern" inherits="Resource" version="4.0">
+ <brief_description>
+ Holds a pattern to be copied from or pasted into [TileMap]s.
+ </brief_description>
+ <description>
+ This resource holds a set of cells to help bulk manipulations of [TileMap].
+ A pattern always start at the [code](0,0)[/code] coordinates and cannot have cells with negative coordinates.
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="get_cell_alternative_tile" qualifiers="const">
+ <return type="int" />
+ <argument index="0" name="coords" type="Vector2i" />
+ <description>
+ Returns the tile alternative ID of the cell at [code]coords[/code].
+ </description>
+ </method>
+ <method name="get_cell_atlas_coords" qualifiers="const">
+ <return type="Vector2i" />
+ <argument index="0" name="coords" type="Vector2i" />
+ <description>
+ Returns the tile atlas coordinates ID of the cell at [code]coords[/code].
+ </description>
+ </method>
+ <method name="get_cell_source_id" qualifiers="const">
+ <return type="int" />
+ <argument index="0" name="coords" type="Vector2i" />
+ <description>
+ Returns the tile source ID of the cell at [code]coords[/code].
+ </description>
+ </method>
+ <method name="get_size" qualifiers="const">
+ <return type="Vector2i" />
+ <description>
+ Returns the size, in cells, of the pattern.
+ </description>
+ </method>
+ <method name="get_used_cells" qualifiers="const">
+ <return type="Vector2i[]" />
+ <description>
+ Returns the list of used cell coordinates in the pattern.
+ </description>
+ </method>
+ <method name="has_cell" qualifiers="const">
+ <return type="bool" />
+ <argument index="0" name="coords" type="Vector2i" />
+ <description>
+ Returns whether the pattern has a tile at the given coordinates.
+ </description>
+ </method>
+ <method name="is_empty" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns whether the pattern is empty or not.
+ </description>
+ </method>
+ <method name="remove_cell">
+ <return type="void" />
+ <argument index="0" name="coords" type="Vector2i" />
+ <argument index="1" name="arg1" type="bool" />
+ <description>
+ Remove the cell at the given coordinates.
+ </description>
+ </method>
+ <method name="set_cell">
+ <return type="void" />
+ <argument index="0" name="coords" type="Vector2i" />
+ <argument index="1" name="source_id" type="int" default="-1" />
+ <argument index="2" name="atlas_coords" type="Vector2i" default="Vector2i(-1, -1)" />
+ <argument index="3" name="alternative_tile" type="int" default="-1" />
+ <description>
+ Sets the tile indentifiers for the cell at coordinates [code]coords[/code]. See [method TileMap.set_cell].
+ </description>
+ </method>
+ <method name="set_size">
+ <return type="void" />
+ <argument index="0" name="size" type="Vector2i" />
+ <description>
+ Sets the size of the pattern.
+ </description>
+ </method>
+ </methods>
+</class>
diff --git a/doc/classes/TileSet.xml b/doc/classes/TileSet.xml
index 02baded019..45d6f9ca6c 100644
--- a/doc/classes/TileSet.xml
+++ b/doc/classes/TileSet.xml
@@ -46,6 +46,14 @@
Occlusion layers allow assigning occlusion polygons to atlas tiles.
</description>
</method>
+ <method name="add_pattern">
+ <return type="int" />
+ <argument index="0" name="pattern" type="TileMapPattern" />
+ <argument index="1" name="index" type="int" default="-1" />
+ <description>
+ Adds a [TileMapPattern] to be stored in the TileSet resouce. If provided, insert it at the given [code]index[/code].
+ </description>
+ </method>
<method name="add_physics_layer">
<return type="void" />
<argument index="0" name="to_position" type="int" default="-1" />
@@ -154,6 +162,19 @@
Returns the occlusion layers count.
</description>
</method>
+ <method name="get_pattern">
+ <return type="TileMapPattern" />
+ <argument index="0" name="index" type="int" default="-1" />
+ <description>
+ Returns the [TileMapPattern] at the given [code]index[/code].
+ </description>
+ </method>
+ <method name="get_patterns_count">
+ <return type="int" />
+ <description>
+ Returns the number of [TileMapPattern] this tile set handles.
+ </description>
+ </method>
<method name="get_physics_layer_collision_layer" qualifiers="const">
<return type="int" />
<argument index="0" name="layer_index" type="int" />
@@ -374,6 +395,13 @@
Removes the occlusion layer at index [code]layer_index[/code]. Also updates the atlas tiles accordingly.
</description>
</method>
+ <method name="remove_pattern">
+ <return type="void" />
+ <argument index="0" name="index" type="int" />
+ <description>
+ Remove the [TileMapPattern] at the given index.
+ </description>
+ </method>
<method name="remove_physics_layer">
<return type="void" />
<argument index="0" name="layer_index" type="int" />
diff --git a/editor/plugins/editor_preview_plugins.h b/editor/plugins/editor_preview_plugins.h
index 6e8b9a34cf..091feae5fb 100644
--- a/editor/plugins/editor_preview_plugins.h
+++ b/editor/plugins/editor_preview_plugins.h
@@ -173,4 +173,22 @@ public:
EditorFontPreviewPlugin();
~EditorFontPreviewPlugin();
};
+
+class EditorTileMapPatternPreviewPlugin : public EditorResourcePreviewGenerator {
+ GDCLASS(EditorTileMapPatternPreviewPlugin, EditorResourcePreviewGenerator);
+
+ mutable SafeFlag preview_done;
+
+ void _preview_done(const Variant &p_udata);
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual bool handles(const String &p_type) const override;
+ virtual Ref<Texture2D> generate(const RES &p_from, const Size2 &p_size) const override;
+
+ EditorTileMapPatternPreviewPlugin();
+ ~EditorTileMapPatternPreviewPlugin();
+};
#endif // EDITORPREVIEWPLUGINS_H
diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp
index 90860759b5..5df4f40b55 100644
--- a/editor/plugins/tiles/tile_map_editor.cpp
+++ b/editor/plugins/tiles/tile_map_editor.cpp
@@ -36,6 +36,7 @@
#include "editor/editor_scale.h"
#include "editor/plugins/canvas_item_editor_plugin.h"
+#include "scene/2d/camera_2d.h"
#include "scene/gui/center_container.h"
#include "scene/gui/split_container.h"
@@ -43,31 +44,11 @@
#include "core/math/geometry_2d.h"
#include "core/os/keyboard.h"
-void TileMapEditorTilesPlugin::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_ENTER_TREE:
- case NOTIFICATION_THEME_CHANGED:
- select_tool_button->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons")));
- paint_tool_button->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")));
- line_tool_button->set_icon(get_theme_icon(SNAME("CurveLinear"), SNAME("EditorIcons")));
- rect_tool_button->set_icon(get_theme_icon(SNAME("Rectangle"), SNAME("EditorIcons")));
- bucket_tool_button->set_icon(get_theme_icon(SNAME("Bucket"), SNAME("EditorIcons")));
-
- picker_button->set_icon(get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons")));
- erase_button->set_icon(get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons")));
-
- missing_atlas_texture_icon = get_theme_icon(SNAME("TileSet"), SNAME("EditorIcons"));
- break;
- case NOTIFICATION_VISIBILITY_CHANGED:
- _stop_dragging();
- break;
- }
-}
-
void TileMapEditorTilesPlugin::tile_set_changed() {
_update_fix_selected_and_hovered();
_update_tile_set_sources_list();
- _update_bottom_panel();
+ _update_source_display();
+ _update_patterns_list();
}
void TileMapEditorTilesPlugin::_on_random_tile_checkbox_toggled(bool p_pressed) {
@@ -125,8 +106,19 @@ void TileMapEditorTilesPlugin::_update_toolbar() {
}
}
-Control *TileMapEditorTilesPlugin::get_toolbar() const {
- return toolbar;
+Vector<TileMapEditorPlugin::TabData> TileMapEditorTilesPlugin::get_tabs() const {
+ Vector<TileMapEditorPlugin::TabData> tabs;
+ tabs.push_back({ toolbar, tiles_bottom_panel });
+ tabs.push_back({ toolbar, patterns_bottom_panel });
+ return tabs;
+}
+
+void TileMapEditorTilesPlugin::_tab_changed() {
+ if (tiles_bottom_panel->is_visible_in_tree()) {
+ _update_selection_pattern_from_tileset_tiles_selection();
+ } else if (patterns_bottom_panel->is_visible_in_tree()) {
+ _update_selection_pattern_from_tileset_pattern_selection();
+ }
}
void TileMapEditorTilesPlugin::_update_tile_set_sources_list() {
@@ -173,7 +165,7 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() {
// Scene collection source.
TileSetScenesCollectionSource *scene_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source);
if (scene_collection_source) {
- texture = get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons"));
+ texture = tiles_bottom_panel->get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons"));
if (item_text.is_empty()) {
item_text = vformat(TTR("Scene Collection Source (ID: %d)"), source_id);
}
@@ -205,7 +197,7 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() {
TilesEditor::get_singleton()->set_sources_lists_current(sources_list->get_current());
}
-void TileMapEditorTilesPlugin::_update_bottom_panel() {
+void TileMapEditorTilesPlugin::_update_source_display() {
// Update the atlas display.
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
if (!tile_map) {
@@ -252,6 +244,81 @@ void TileMapEditorTilesPlugin::_update_bottom_panel() {
}
}
+void TileMapEditorTilesPlugin::_patterns_item_list_gui_input(const Ref<InputEvent> &p_event) {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ if (ED_IS_SHORTCUT("tiles_editor/paste", p_event) && p_event->is_pressed() && !p_event->is_echo()) {
+ select_last_pattern = true;
+ int new_pattern_index = tile_set->get_patterns_count();
+ undo_redo->create_action(TTR("Add TileSet pattern"));
+ undo_redo->add_do_method(*tile_set, "add_pattern", tile_map_clipboard, new_pattern_index);
+ undo_redo->add_undo_method(*tile_set, "remove_pattern", new_pattern_index);
+ undo_redo->commit_action();
+ patterns_item_list->accept_event();
+ }
+
+ if (ED_IS_SHORTCUT("tiles_editor/delete", p_event) && p_event->is_pressed() && !p_event->is_echo()) {
+ Vector<int> selected = patterns_item_list->get_selected_items();
+ undo_redo->create_action(TTR("Remove TileSet patterns"));
+ for (int i = 0; i < selected.size(); i++) {
+ int pattern_index = selected[i];
+ undo_redo->add_do_method(*tile_set, "remove_pattern", pattern_index);
+ undo_redo->add_undo_method(*tile_set, "add_pattern", tile_set->get_pattern(pattern_index), pattern_index);
+ }
+ undo_redo->commit_action();
+ patterns_item_list->accept_event();
+ }
+}
+
+void TileMapEditorTilesPlugin::_pattern_preview_done(Ref<TileMapPattern> p_pattern, Ref<Texture2D> p_texture) {
+ // TODO optimize ?
+ for (int i = 0; i < patterns_item_list->get_item_count(); i++) {
+ if (patterns_item_list->get_item_metadata(i) == p_pattern) {
+ patterns_item_list->set_item_icon(i, p_texture);
+ break;
+ }
+ }
+}
+
+void TileMapEditorTilesPlugin::_update_patterns_list() {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ // Recreate the items.
+ patterns_item_list->clear();
+ for (int i = 0; i < tile_set->get_patterns_count(); i++) {
+ int id = patterns_item_list->add_item("");
+ patterns_item_list->set_item_metadata(id, tile_set->get_pattern(i));
+ TilesEditor::get_singleton()->queue_pattern_preview(tile_set, tile_set->get_pattern(i), callable_mp(this, &TileMapEditorTilesPlugin::_pattern_preview_done));
+ }
+
+ // Update the label visibility.
+ patterns_help_label->set_visible(patterns_item_list->get_item_count() == 0);
+
+ // Added a new pattern, thus select the last one.
+ if (select_last_pattern) {
+ patterns_item_list->select(tile_set->get_patterns_count() - 1);
+ patterns_item_list->grab_focus();
+ _update_selection_pattern_from_tileset_pattern_selection();
+ }
+ select_last_pattern = false;
+}
+
void TileMapEditorTilesPlugin::_update_atlas_view() {
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
if (!tile_map) {
@@ -304,7 +371,7 @@ void TileMapEditorTilesPlugin::_update_scenes_collection_view() {
Variant udata = i;
EditorResourcePreview::get_singleton()->queue_edited_resource_preview(scene, this, "_scene_thumbnail_done", udata);
} else {
- item_index = scene_tiles_list->add_item(TTR("Tile with Invalid Scene"), get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons")));
+ item_index = scene_tiles_list->add_item(TTR("Tile with Invalid Scene"), tiles_bottom_panel->get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons")));
}
scene_tiles_list->set_item_metadata(item_index, scene_id);
@@ -360,19 +427,32 @@ void TileMapEditorTilesPlugin::_scenes_list_multi_selected(int p_index, bool p_s
}
}
- _update_selection_pattern_from_tileset_selection();
+ _update_selection_pattern_from_tileset_tiles_selection();
}
void TileMapEditorTilesPlugin::_scenes_list_nothing_selected() {
scene_tiles_list->deselect_all();
tile_set_selection.clear();
tile_map_selection.clear();
- selection_pattern->clear();
- _update_selection_pattern_from_tileset_selection();
+ selection_pattern.instantiate();
+ _update_selection_pattern_from_tileset_tiles_selection();
+}
+
+void TileMapEditorTilesPlugin::_update_theme() {
+ select_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons")));
+ paint_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")));
+ line_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("CurveLinear"), SNAME("EditorIcons")));
+ rect_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Rectangle"), SNAME("EditorIcons")));
+ bucket_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Bucket"), SNAME("EditorIcons")));
+
+ picker_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons")));
+ erase_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons")));
+
+ missing_atlas_texture_icon = tiles_bottom_panel->get_theme_icon(SNAME("TileSet"), SNAME("EditorIcons"));
}
bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
- if (!is_visible_in_tree()) {
+ if (!(tiles_bottom_panel->is_visible_in_tree() || patterns_bottom_panel->is_visible_in_tree())) {
// If the bottom editor is not visible, we ignore inputs.
return false;
}
@@ -400,7 +480,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p
if (ED_IS_SHORTCUT("tiles_editor/cut", p_event) || ED_IS_SHORTCUT("tiles_editor/copy", p_event)) {
// Fill in the clipboard.
if (!tile_map_selection.is_empty()) {
- memdelete(tile_map_clipboard);
+ tile_map_clipboard.instantiate();
TypedArray<Vector2i> coords_array;
for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
coords_array.push_back(E->get());
@@ -633,7 +713,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over
Vector2i tile_shape_size = tile_set->get_tile_size();
// Draw the selection.
- if (is_visible_in_tree() && tool_buttons_group->get_pressed_button() == select_tool_button) {
+ if ((tiles_bottom_panel->is_visible_in_tree() || patterns_bottom_panel->is_visible_in_tree()) && tool_buttons_group->get_pressed_button() == select_tool_button) {
// In select mode, we only draw the current selection if we are modifying it (pressing control or shift).
if (drag_type == DRAG_TYPE_MOVE || (drag_type == DRAG_TYPE_SELECT && !Input::get_singleton()->is_key_pressed(KEY_CTRL) && !Input::get_singleton()->is_key_pressed(KEY_SHIFT))) {
// Do nothing
@@ -645,7 +725,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over
}
// Handle the preview of the tiles to be placed.
- if (is_visible_in_tree() && has_mouse) { // Only if the tilemap editor is opened and the viewport is hovered.
+ if ((tiles_bottom_panel->is_visible_in_tree() || patterns_bottom_panel->is_visible_in_tree()) && has_mouse) { // Only if the tilemap editor is opened and the viewport is hovered.
Map<Vector2i, TileMapCell> preview;
Rect2i drawn_grid_rect;
@@ -679,21 +759,23 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over
}
tile_map->draw_cells_outline(p_overlay, to_draw, Color(1.0, 1.0, 1.0), xform);
} else if (drag_type == DRAG_TYPE_MOVE) {
- // Preview when moving.
- Vector2i top_left;
- if (!tile_map_selection.is_empty()) {
- top_left = tile_map_selection.front()->get();
- }
- for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
- top_left = top_left.min(E->get());
- }
- Vector2i offset = drag_start_mouse_pos - tile_map->map_to_world(top_left);
- offset = tile_map->world_to_map(drag_last_mouse_pos - offset) - tile_map->world_to_map(drag_start_mouse_pos - offset);
+ if (!(patterns_item_list->is_visible_in_tree() && patterns_item_list->has_point(patterns_item_list->get_local_mouse_position()))) {
+ // Preview when moving.
+ Vector2i top_left;
+ if (!tile_map_selection.is_empty()) {
+ top_left = tile_map_selection.front()->get();
+ }
+ for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
+ top_left = top_left.min(E->get());
+ }
+ Vector2i offset = drag_start_mouse_pos - tile_map->map_to_world(top_left);
+ offset = tile_map->world_to_map(drag_last_mouse_pos - offset) - tile_map->world_to_map(drag_start_mouse_pos - offset);
- TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells();
- for (int i = 0; i < selection_used_cells.size(); i++) {
- Vector2i coords = tile_map->map_pattern(offset + top_left, selection_used_cells[i], selection_pattern);
- preview[coords] = TileMapCell(selection_pattern->get_cell_source_id(selection_used_cells[i]), selection_pattern->get_cell_atlas_coords(selection_used_cells[i]), selection_pattern->get_cell_alternative_tile(selection_used_cells[i]));
+ TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells();
+ for (int i = 0; i < selection_used_cells.size(); i++) {
+ Vector2i coords = tile_map->map_pattern(offset + top_left, selection_used_cells[i], selection_pattern);
+ preview[coords] = TileMapCell(selection_pattern->get_cell_source_id(selection_used_cells[i]), selection_pattern->get_cell_atlas_coords(selection_used_cells[i]), selection_pattern->get_cell_alternative_tile(selection_used_cells[i]));
+ }
}
} else if (drag_type == DRAG_TYPE_CLIPBOARD_PASTE) {
// Preview when pasting.
@@ -832,7 +914,7 @@ void TileMapEditorTilesPlugin::_mouse_exited_viewport() {
CanvasItemEditor::get_singleton()->update_viewport();
}
-TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(const TileMapPattern *p_pattern) {
+TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(Ref<TileMapPattern> p_pattern) {
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
if (!tile_map) {
return TileMapCell();
@@ -896,9 +978,10 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_
}
// Get or create the pattern.
- TileMapPattern erase_pattern;
- erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
- TileMapPattern *pattern = _is_erasing() ? &erase_pattern : selection_pattern;
+ Ref<TileMapPattern> erase_pattern;
+ erase_pattern.instantiate();
+ erase_pattern->set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
+ Ref<TileMapPattern> pattern = _is_erasing() ? erase_pattern : selection_pattern;
Map<Vector2i, TileMapCell> output;
if (!pattern->is_empty()) {
@@ -948,9 +1031,11 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start
rect.size += Vector2i(1, 1);
// Get or create the pattern.
- TileMapPattern erase_pattern;
- erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
- TileMapPattern *pattern = _is_erasing() ? &erase_pattern : selection_pattern;
+ Ref<TileMapPattern> erase_pattern;
+ erase_pattern.instantiate();
+ erase_pattern->set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
+ Ref<TileMapPattern> pattern = _is_erasing() ? erase_pattern : selection_pattern;
+
Map<Vector2i, TileMapCell> err_output;
ERR_FAIL_COND_V(pattern->is_empty(), err_output);
@@ -1007,9 +1092,10 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i
}
// Get or create the pattern.
- TileMapPattern erase_pattern;
- erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
- TileMapPattern *pattern = _is_erasing() ? &erase_pattern : selection_pattern;
+ Ref<TileMapPattern> erase_pattern;
+ erase_pattern.instantiate();
+ erase_pattern->set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
+ Ref<TileMapPattern> pattern = _is_erasing() ? erase_pattern : selection_pattern;
if (!pattern->is_empty()) {
TileMapCell source = tile_map->get_cell(tile_map_layer, p_coords);
@@ -1153,60 +1239,80 @@ void TileMapEditorTilesPlugin::_stop_dragging() {
_update_tileset_selection_from_selection_pattern();
} break;
case DRAG_TYPE_MOVE: {
- Vector2i top_left;
- if (!tile_map_selection.is_empty()) {
- top_left = tile_map_selection.front()->get();
- }
- for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
- top_left = top_left.min(E->get());
- }
+ if (patterns_item_list->is_visible_in_tree() && patterns_item_list->has_point(patterns_item_list->get_local_mouse_position())) {
+ // Restore the cells.
+ for (KeyValue<Vector2i, TileMapCell> kv : drag_modified) {
+ tile_map->set_cell(tile_map_layer, kv.key, kv.value.source_id, kv.value.get_atlas_coords(), kv.value.alternative_tile);
+ }
- Vector2i offset = drag_start_mouse_pos - tile_map->map_to_world(top_left);
- offset = tile_map->world_to_map(mpos - offset) - tile_map->world_to_map(drag_start_mouse_pos - offset);
+ // Creating a pattern in the pattern list.
+ select_last_pattern = true;
+ int new_pattern_index = tile_set->get_patterns_count();
+ undo_redo->create_action(TTR("Add TileSet pattern"));
+ undo_redo->add_do_method(*tile_set, "add_pattern", selection_pattern, new_pattern_index);
+ undo_redo->add_undo_method(*tile_set, "remove_pattern", new_pattern_index);
+ undo_redo->commit_action();
+ } else {
+ // Get the top-left cell.
+ Vector2i top_left;
+ if (!tile_map_selection.is_empty()) {
+ top_left = tile_map_selection.front()->get();
+ }
+ for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
+ top_left = top_left.min(E->get());
+ }
- TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells();
+ // Get the offset from the mouse.
+ Vector2i offset = drag_start_mouse_pos - tile_map->map_to_world(top_left);
+ offset = tile_map->world_to_map(mpos - offset) - tile_map->world_to_map(drag_start_mouse_pos - offset);
- Vector2i coords;
- Map<Vector2i, TileMapCell> cells_undo;
- for (int i = 0; i < selection_used_cells.size(); i++) {
- coords = tile_map->map_pattern(top_left, selection_used_cells[i], selection_pattern);
- cells_undo[coords] = TileMapCell(drag_modified[coords].source_id, drag_modified[coords].get_atlas_coords(), drag_modified[coords].alternative_tile);
- coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
- cells_undo[coords] = TileMapCell(tile_map->get_cell_source_id(tile_map_layer, coords), tile_map->get_cell_atlas_coords(tile_map_layer, coords), tile_map->get_cell_alternative_tile(tile_map_layer, coords));
- }
+ TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells();
- Map<Vector2i, TileMapCell> cells_do;
- for (int i = 0; i < selection_used_cells.size(); i++) {
- coords = tile_map->map_pattern(top_left, selection_used_cells[i], selection_pattern);
- cells_do[coords] = TileMapCell();
- }
- for (int i = 0; i < selection_used_cells.size(); i++) {
- coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
- cells_do[coords] = TileMapCell(selection_pattern->get_cell_source_id(selection_used_cells[i]), selection_pattern->get_cell_atlas_coords(selection_used_cells[i]), selection_pattern->get_cell_alternative_tile(selection_used_cells[i]));
- }
- undo_redo->create_action(TTR("Move tiles"));
- // Move the tiles.
- for (const KeyValue<Vector2i, TileMapCell> &E : cells_do) {
- undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile);
- }
- for (const KeyValue<Vector2i, TileMapCell> &E : cells_undo) {
- undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile);
- }
+ // Build the list of cells to undo.
+ Vector2i coords;
+ Map<Vector2i, TileMapCell> cells_undo;
+ for (int i = 0; i < selection_used_cells.size(); i++) {
+ coords = tile_map->map_pattern(top_left, selection_used_cells[i], selection_pattern);
+ cells_undo[coords] = TileMapCell(drag_modified[coords].source_id, drag_modified[coords].get_atlas_coords(), drag_modified[coords].alternative_tile);
+ coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
+ cells_undo[coords] = TileMapCell(tile_map->get_cell_source_id(tile_map_layer, coords), tile_map->get_cell_atlas_coords(tile_map_layer, coords), tile_map->get_cell_alternative_tile(tile_map_layer, coords));
+ }
- // Update the selection.
- undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection());
- tile_map_selection.clear();
- for (int i = 0; i < selection_used_cells.size(); i++) {
- coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
- tile_map_selection.insert(coords);
+ // Build the list of cells to do.
+ Map<Vector2i, TileMapCell> cells_do;
+ for (int i = 0; i < selection_used_cells.size(); i++) {
+ coords = tile_map->map_pattern(top_left, selection_used_cells[i], selection_pattern);
+ cells_do[coords] = TileMapCell();
+ }
+ for (int i = 0; i < selection_used_cells.size(); i++) {
+ coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
+ cells_do[coords] = TileMapCell(selection_pattern->get_cell_source_id(selection_used_cells[i]), selection_pattern->get_cell_atlas_coords(selection_used_cells[i]), selection_pattern->get_cell_alternative_tile(selection_used_cells[i]));
+ }
+
+ // Move the tiles.
+ undo_redo->create_action(TTR("Move tiles"));
+ for (Map<Vector2i, TileMapCell>::Element *E = cells_do.front(); E; E = E->next()) {
+ undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ for (Map<Vector2i, TileMapCell>::Element *E = cells_undo.front(); E; E = E->next()) {
+ undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+
+ // Update the selection.
+ undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection());
+ tile_map_selection.clear();
+ for (int i = 0; i < selection_used_cells.size(); i++) {
+ coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
+ tile_map_selection.insert(coords);
+ }
+ undo_redo->add_do_method(this, "_set_tile_map_selection", _get_tile_map_selection());
+ undo_redo->commit_action();
}
- undo_redo->add_do_method(this, "_set_tile_map_selection", _get_tile_map_selection());
- undo_redo->commit_action();
} break;
case DRAG_TYPE_PICK: {
Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos) - tile_map->world_to_map(drag_start_mouse_pos)).abs();
rect.size += Vector2i(1, 1);
- memdelete(selection_pattern);
+
TypedArray<Vector2i> coords_array;
for (int x = rect.position.x; x < rect.get_end().x; x++) {
for (int y = rect.position.y; y < rect.get_end().y; y++) {
@@ -1216,11 +1322,10 @@ void TileMapEditorTilesPlugin::_stop_dragging() {
}
}
}
- selection_pattern = tile_map->get_pattern(tile_map_layer, coords_array);
- if (!selection_pattern->is_empty()) {
+ Ref<TileMapPattern> new_selection_pattern = tile_map->get_pattern(tile_map_layer, coords_array);
+ if (!new_selection_pattern->is_empty()) {
+ selection_pattern = new_selection_pattern;
_update_tileset_selection_from_selection_pattern();
- } else {
- _update_selection_pattern_from_tileset_selection();
}
picker_button->set_pressed(false);
} break;
@@ -1288,8 +1393,9 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() {
hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
tile_set_selection.clear();
+ patterns_item_list->deselect_all();
tile_map_selection.clear();
- selection_pattern->clear();
+ selection_pattern.instantiate();
return;
}
@@ -1299,8 +1405,9 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() {
hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
tile_set_selection.clear();
+ patterns_item_list->deselect_all();
tile_map_selection.clear();
- selection_pattern->clear();
+ selection_pattern.instantiate();
return;
}
@@ -1310,8 +1417,9 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() {
hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
tile_set_selection.clear();
+ patterns_item_list->deselect_all();
tile_map_selection.clear();
- selection_pattern->clear();
+ selection_pattern.instantiate();
return;
}
@@ -1339,8 +1447,10 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() {
if (!tile_map_selection.is_empty()) {
_update_selection_pattern_from_tilemap_selection();
+ } else if (tiles_bottom_panel->is_visible_in_tree()) {
+ _update_selection_pattern_from_tileset_tiles_selection();
} else {
- _update_selection_pattern_from_tileset_selection();
+ _update_selection_pattern_from_tileset_pattern_selection();
}
}
@@ -1373,9 +1483,14 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tilemap_selection(
return;
}
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
ERR_FAIL_INDEX(tile_map_layer, tile_map->get_layers_count());
- memdelete(selection_pattern);
+ selection_pattern.instantiate();
TypedArray<Vector2i> coords_array;
for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
@@ -1384,7 +1499,7 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tilemap_selection(
selection_pattern = tile_map->get_pattern(tile_map_layer, coords_array);
}
-void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection() {
+void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_tiles_selection() {
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
if (!tile_map) {
return;
@@ -1399,7 +1514,7 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection(
tile_map_selection.clear();
// Clear the selected pattern.
- selection_pattern->clear();
+ selection_pattern.instantiate();
// Group per source.
Map<int, List<const TileMapCell *>> per_source;
@@ -1457,6 +1572,30 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection(
CanvasItemEditor::get_singleton()->update_viewport();
}
+void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_pattern_selection() {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ // Clear the tilemap selection.
+ tile_map_selection.clear();
+
+ // Clear the selected pattern.
+ selection_pattern.instantiate();
+
+ if (patterns_item_list->get_selected_items().size() >= 1) {
+ selection_pattern = patterns_item_list->get_item_metadata(patterns_item_list->get_selected_items()[0]);
+ }
+
+ CanvasItemEditor::get_singleton()->update_viewport();
+}
+
void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern() {
tile_set_selection.clear();
TypedArray<Vector2i> used_cells = selection_pattern->get_used_cells();
@@ -1466,7 +1605,7 @@ void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern(
tile_set_selection.insert(TileMapCell(selection_pattern->get_cell_source_id(coords), selection_pattern->get_cell_atlas_coords(coords), selection_pattern->get_cell_alternative_tile(coords)));
}
}
- _update_bottom_panel();
+ _update_source_display();
tile_atlas_control->update();
alternative_tiles_control->update();
}
@@ -1616,7 +1755,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven
tile_set_selection.insert(TileMapCell(source_id, hovered_tile.get_atlas_coords(), 0));
}
}
- _update_selection_pattern_from_tileset_selection();
+ _update_selection_pattern_from_tileset_tiles_selection();
} else { // Released
if (tile_set_dragging_selection) {
if (!mb->is_shift_pressed()) {
@@ -1653,7 +1792,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven
}
}
}
- _update_selection_pattern_from_tileset_selection();
+ _update_selection_pattern_from_tileset_tiles_selection();
}
tile_set_dragging_selection = false;
}
@@ -1773,7 +1912,7 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_gui_input(const Ref<In
tile_set_selection.insert(TileMapCell(source_id, hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile));
}
}
- _update_selection_pattern_from_tileset_selection();
+ _update_selection_pattern_from_tileset_tiles_selection();
}
tile_atlas_control->update();
alternative_tiles_control->update();
@@ -1806,8 +1945,9 @@ void TileMapEditorTilesPlugin::edit(ObjectID p_tile_map_id, int p_tile_map_layer
// Clear the selection.
tile_set_selection.clear();
+ patterns_item_list->deselect_all();
tile_map_selection.clear();
- selection_pattern->clear();
+ selection_pattern.instantiate();
}
tile_map_layer = p_tile_map_layer;
@@ -1829,9 +1969,13 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
ED_SHORTCUT("tiles_editor/cancel", TTR("Cancel"), KEY_ESCAPE);
ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), KEY_DELETE);
+ // --- Initialize references ---
+ tile_map_clipboard.instantiate();
+ selection_pattern.instantiate();
+
// --- Toolbar ---
toolbar = memnew(HBoxContainer);
- toolbar->set_h_size_flags(SIZE_EXPAND_FILL);
+ toolbar->set_h_size_flags(Control::SIZE_EXPAND_FILL);
HBoxContainer *tilemap_tiles_tools_buttons = memnew(HBoxContainer);
@@ -1942,39 +2086,44 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
paint_tool_button->set_pressed(true);
_update_toolbar();
- // --- Bottom panel ---
- set_name("Tiles");
+ // --- Bottom panel tiles ---
+ tiles_bottom_panel = memnew(VBoxContainer);
+ tiles_bottom_panel->connect("tree_entered", callable_mp(this, &TileMapEditorTilesPlugin::_update_theme));
+ tiles_bottom_panel->connect("theme_changed", callable_mp(this, &TileMapEditorTilesPlugin::_update_theme));
+ tiles_bottom_panel->connect("visibility_changed", callable_mp(this, &TileMapEditorTilesPlugin::_stop_dragging));
+ tiles_bottom_panel->connect("visibility_changed", callable_mp(this, &TileMapEditorTilesPlugin::_tab_changed));
+ tiles_bottom_panel->set_name(TTR("Tiles"));
missing_source_label = memnew(Label);
missing_source_label->set_text(TTR("This TileMap's TileSet has no source configured. Edit the TileSet resource to add one."));
- missing_source_label->set_h_size_flags(SIZE_EXPAND_FILL);
- missing_source_label->set_v_size_flags(SIZE_EXPAND_FILL);
+ missing_source_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ missing_source_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);
missing_source_label->set_align(Label::ALIGN_CENTER);
missing_source_label->set_valign(Label::VALIGN_CENTER);
missing_source_label->hide();
- add_child(missing_source_label);
+ tiles_bottom_panel->add_child(missing_source_label);
atlas_sources_split_container = memnew(HSplitContainer);
- atlas_sources_split_container->set_h_size_flags(SIZE_EXPAND_FILL);
- atlas_sources_split_container->set_v_size_flags(SIZE_EXPAND_FILL);
- add_child(atlas_sources_split_container);
+ atlas_sources_split_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ atlas_sources_split_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ tiles_bottom_panel->add_child(atlas_sources_split_container);
sources_list = memnew(ItemList);
sources_list->set_fixed_icon_size(Size2i(60, 60) * EDSCALE);
- sources_list->set_h_size_flags(SIZE_EXPAND_FILL);
+ sources_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
sources_list->set_stretch_ratio(0.25);
sources_list->set_custom_minimum_size(Size2i(70, 0) * EDSCALE);
sources_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_fix_selected_and_hovered).unbind(1));
- sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_bottom_panel).unbind(1));
+ sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_source_display).unbind(1));
sources_list->connect("item_selected", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_sources_lists_current));
sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_sources_list), varray(sources_list));
atlas_sources_split_container->add_child(sources_list);
// Tile atlas source.
tile_atlas_view = memnew(TileAtlasView);
- tile_atlas_view->set_h_size_flags(SIZE_EXPAND_FILL);
- tile_atlas_view->set_v_size_flags(SIZE_EXPAND_FILL);
+ tile_atlas_view->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ tile_atlas_view->set_v_size_flags(Control::SIZE_EXPAND_FILL);
tile_atlas_view->set_texture_grid_visible(false);
tile_atlas_view->set_tile_shape_grid_visible(false);
tile_atlas_view->connect("transform_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_view_transform));
@@ -1994,8 +2143,8 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
// Scenes collection source.
scene_tiles_list = memnew(ItemList);
- scene_tiles_list->set_h_size_flags(SIZE_EXPAND_FILL);
- scene_tiles_list->set_v_size_flags(SIZE_EXPAND_FILL);
+ scene_tiles_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ scene_tiles_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
scene_tiles_list->set_drag_forwarding(this);
scene_tiles_list->set_select_mode(ItemList::SELECT_MULTI);
scene_tiles_list->connect("multi_selected", callable_mp(this, &TileMapEditorTilesPlugin::_scenes_list_multi_selected));
@@ -2006,30 +2155,42 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
// Invalid source label.
invalid_source_label = memnew(Label);
invalid_source_label->set_text(TTR("Invalid source selected."));
- invalid_source_label->set_h_size_flags(SIZE_EXPAND_FILL);
- invalid_source_label->set_v_size_flags(SIZE_EXPAND_FILL);
+ invalid_source_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ invalid_source_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);
invalid_source_label->set_align(Label::ALIGN_CENTER);
invalid_source_label->set_valign(Label::VALIGN_CENTER);
invalid_source_label->hide();
atlas_sources_split_container->add_child(invalid_source_label);
- _update_bottom_panel();
+ // --- Bottom panel patterns ---
+ patterns_bottom_panel = memnew(VBoxContainer);
+ patterns_bottom_panel->set_name(TTR("Patterns"));
+ patterns_bottom_panel->connect("visibility_changed", callable_mp(this, &TileMapEditorTilesPlugin::_tab_changed));
+
+ int thumbnail_size = 64;
+ patterns_item_list = memnew(ItemList);
+ patterns_item_list->set_max_columns(0);
+ patterns_item_list->set_icon_mode(ItemList::ICON_MODE_TOP);
+ patterns_item_list->set_fixed_column_width(thumbnail_size * 3 / 2);
+ patterns_item_list->set_max_text_lines(2);
+ patterns_item_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size));
+ patterns_item_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ patterns_item_list->connect("gui_input", callable_mp(this, &TileMapEditorTilesPlugin::_patterns_item_list_gui_input));
+ patterns_item_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_pattern_selection).unbind(1));
+ patterns_item_list->connect("item_activated", callable_mp(this, &TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_pattern_selection));
+ patterns_item_list->connect("nothing_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_pattern_selection));
+ patterns_bottom_panel->add_child(patterns_item_list);
+
+ patterns_help_label = memnew(Label);
+ patterns_help_label->set_text(TTR("Drag and drop or paste a TileMap selection here to store a pattern."));
+ patterns_help_label->set_anchors_and_offsets_preset(Control::PRESET_CENTER);
+ patterns_item_list->add_child(patterns_help_label);
+
+ // Update.
+ _update_source_display();
}
TileMapEditorTilesPlugin::~TileMapEditorTilesPlugin() {
- memdelete(selection_pattern);
- memdelete(tile_map_clipboard);
-}
-
-void TileMapEditorTerrainsPlugin::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_ENTER_TREE:
- case NOTIFICATION_THEME_CHANGED:
- paint_tool_button->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")));
- picker_button->set_icon(get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons")));
- erase_button->set_icon(get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons")));
- break;
- }
}
void TileMapEditorTerrainsPlugin::tile_set_changed() {
@@ -2052,8 +2213,10 @@ void TileMapEditorTerrainsPlugin::_update_toolbar() {
}
}
-Control *TileMapEditorTerrainsPlugin::get_toolbar() const {
- return toolbar;
+Vector<TileMapEditorPlugin::TabData> TileMapEditorTerrainsPlugin::get_tabs() const {
+ Vector<TileMapEditorPlugin::TabData> tabs;
+ tabs.push_back({ toolbar, main_vbox_container });
+ return tabs;
}
Map<Vector2i, TileSet::CellNeighbor> TileMapEditorTerrainsPlugin::Constraint::get_overlapping_coords_and_peering_bits() const {
@@ -2811,7 +2974,7 @@ void TileMapEditorTerrainsPlugin::_stop_dragging() {
}
bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
- if (!is_visible_in_tree()) {
+ if (!main_vbox_container->is_visible_in_tree()) {
// If the bottom editor is not visible, we ignore inputs.
return false;
}
@@ -3083,13 +3246,13 @@ void TileMapEditorTerrainsPlugin::_update_terrains_tree() {
TreeItem *terrain_set_tree_item = terrains_tree->create_item();
String matches;
if (tile_set->get_terrain_set_mode(terrain_set_index) == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) {
- terrain_set_tree_item->set_icon(0, get_theme_icon(SNAME("TerrainMatchCornersAndSides"), SNAME("EditorIcons")));
+ terrain_set_tree_item->set_icon(0, main_vbox_container->get_theme_icon(SNAME("TerrainMatchCornersAndSides"), SNAME("EditorIcons")));
matches = String(TTR("Matches Corners and Sides"));
} else if (tile_set->get_terrain_set_mode(terrain_set_index) == TileSet::TERRAIN_MODE_MATCH_CORNERS) {
- terrain_set_tree_item->set_icon(0, get_theme_icon(SNAME("TerrainMatchCorners"), SNAME("EditorIcons")));
+ terrain_set_tree_item->set_icon(0, main_vbox_container->get_theme_icon(SNAME("TerrainMatchCorners"), SNAME("EditorIcons")));
matches = String(TTR("Matches Corners Only"));
} else {
- terrain_set_tree_item->set_icon(0, get_theme_icon(SNAME("TerrainMatchSides"), SNAME("EditorIcons")));
+ terrain_set_tree_item->set_icon(0, main_vbox_container->get_theme_icon(SNAME("TerrainMatchSides"), SNAME("EditorIcons")));
matches = String(TTR("Matches Sides Only"));
}
terrain_set_tree_item->set_text(0, vformat("Terrain Set %d (%s)", terrain_set_index, matches));
@@ -3194,6 +3357,12 @@ void TileMapEditorTerrainsPlugin::_update_tiles_list() {
}
}
+void TileMapEditorTerrainsPlugin::_update_theme() {
+ paint_tool_button->set_icon(main_vbox_container->get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")));
+ picker_button->set_icon(main_vbox_container->get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons")));
+ erase_button->set_icon(main_vbox_container->get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons")));
+}
+
void TileMapEditorTerrainsPlugin::edit(ObjectID p_tile_map_id, int p_tile_map_layer) {
_stop_dragging(); // Avoids staying in a wrong drag state.
@@ -3206,15 +3375,18 @@ void TileMapEditorTerrainsPlugin::edit(ObjectID p_tile_map_id, int p_tile_map_la
}
TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() {
- set_name("Terrains");
+ main_vbox_container = memnew(VBoxContainer);
+ main_vbox_container->connect("tree_entered", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_theme));
+ main_vbox_container->connect("theme_changed", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_theme));
+ main_vbox_container->set_name("Terrains");
HSplitContainer *tilemap_tab_terrains = memnew(HSplitContainer);
- tilemap_tab_terrains->set_h_size_flags(SIZE_EXPAND_FILL);
- tilemap_tab_terrains->set_v_size_flags(SIZE_EXPAND_FILL);
- add_child(tilemap_tab_terrains);
+ tilemap_tab_terrains->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ tilemap_tab_terrains->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ main_vbox_container->add_child(tilemap_tab_terrains);
terrains_tree = memnew(Tree);
- terrains_tree->set_h_size_flags(SIZE_EXPAND_FILL);
+ terrains_tree->set_h_size_flags(Control::SIZE_EXPAND_FILL);
terrains_tree->set_stretch_ratio(0.25);
terrains_tree->set_custom_minimum_size(Size2i(70, 0) * EDSCALE);
terrains_tree->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
@@ -3223,7 +3395,7 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() {
tilemap_tab_terrains->add_child(terrains_tree);
terrains_tile_list = memnew(ItemList);
- terrains_tile_list->set_h_size_flags(SIZE_EXPAND_FILL);
+ terrains_tile_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
terrains_tile_list->set_max_columns(0);
terrains_tile_list->set_same_column_width(true);
terrains_tile_list->set_fixed_icon_size(Size2(30, 30) * EDSCALE);
@@ -3290,7 +3462,7 @@ void TileMapEditor::_notification(int p_what) {
if (is_visible_in_tree() && tileset_changed_needs_update) {
_update_bottom_panel();
_update_layers_selection();
- tile_map_editor_plugins[tabs->get_current_tab()]->tile_set_changed();
+ tabs_plugins[tabs_bar->get_current_tab()]->tile_set_changed();
CanvasItemEditor::get_singleton()->update_viewport();
tileset_changed_needs_update = false;
}
@@ -3414,14 +3586,11 @@ void TileMapEditor::_update_bottom_panel() {
// Update the visibility of controls.
missing_tileset_label->set_visible(!tile_set.is_valid());
- if (!tile_set.is_valid()) {
- for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
- tile_map_editor_plugins[i]->hide();
- }
- } else {
- for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
- tile_map_editor_plugins[i]->set_visible(i == tabs->get_current_tab());
- }
+ for (unsigned int tab_index = 0; tab_index < tabs_data.size(); tab_index++) {
+ tabs_data[tab_index].panel->hide();
+ }
+ if (tile_set.is_valid()) {
+ tabs_data[tabs_bar->get_current_tab()].panel->show();
}
}
@@ -3504,27 +3673,25 @@ void TileMapEditor::_tile_map_changed() {
void TileMapEditor::_tab_changed(int p_tab_id) {
// Make the plugin edit the correct tilemap.
- tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id, tile_map_layer);
+ tabs_plugins[tabs_bar->get_current_tab()]->edit(tile_map_id, tile_map_layer);
// Update toolbar.
- for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
- tile_map_editor_plugins[i]->get_toolbar()->set_visible(i == p_tab_id);
+ for (unsigned int tab_index = 0; tab_index < tabs_data.size(); tab_index++) {
+ tabs_data[tab_index].toolbar->hide();
}
+ tabs_data[p_tab_id].toolbar->show();
// Update visible panel.
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
- if (!tile_map || !tile_map->get_tileset().is_valid()) {
- for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
- tile_map_editor_plugins[i]->hide();
- }
- } else {
- for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
- tile_map_editor_plugins[i]->set_visible(i == tabs->get_current_tab());
- }
+ for (unsigned int tab_index = 0; tab_index < tabs_data.size(); tab_index++) {
+ tabs_data[tab_index].panel->hide();
+ }
+ if (tile_map && tile_map->get_tileset().is_valid()) {
+ tabs_data[tabs_bar->get_current_tab()].panel->show();
}
// Graphical update.
- tile_map_editor_plugins[tabs->get_current_tab()]->update();
+ tabs_data[tabs_bar->get_current_tab()].panel->update();
CanvasItemEditor::get_singleton()->update_viewport();
}
@@ -3611,7 +3778,7 @@ void TileMapEditor::_update_layers_selection() {
layers_selection_button->set_custom_minimum_size(min_button_size);
layers_selection_button->update();
- tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id, tile_map_layer);
+ tabs_plugins[tabs_bar->get_current_tab()]->edit(tile_map_id, tile_map_layer);
}
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) {
@@ -3697,7 +3864,7 @@ bool TileMapEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
return true;
}
- return tile_map_editor_plugins[tabs->get_current_tab()]->forward_canvas_gui_input(p_event);
+ return tabs_plugins[tabs_bar->get_current_tab()]->forward_canvas_gui_input(p_event);
}
void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
@@ -3832,7 +3999,7 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
}*/
// Draw the plugins.
- tile_map_editor_plugins[tabs->get_current_tab()]->forward_canvas_draw_over_viewport(p_overlay);
+ tabs_plugins[tabs_bar->get_current_tab()]->forward_canvas_draw_over_viewport(p_overlay);
}
void TileMapEditor::edit(TileMap *p_tile_map) {
@@ -3866,7 +4033,7 @@ void TileMapEditor::edit(TileMap *p_tile_map) {
_update_layers_selection();
// Call the plugins.
- tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id, tile_map_layer);
+ tabs_plugins[tabs_bar->get_current_tab()]->edit(tile_map_id, tile_map_layer);
_tile_map_changed();
}
@@ -3883,24 +4050,31 @@ TileMapEditor::TileMapEditor() {
tile_map_editor_plugins.push_back(memnew(TileMapEditorTerrainsPlugin));
// Tabs.
- tabs = memnew(Tabs);
- tabs->set_clip_tabs(false);
- for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
- tabs->add_tab(tile_map_editor_plugins[i]->get_name());
+ tabs_bar = memnew(Tabs);
+ tabs_bar->set_clip_tabs(false);
+ for (int plugin_index = 0; plugin_index < tile_map_editor_plugins.size(); plugin_index++) {
+ Vector<TileMapEditorPlugin::TabData> tabs_vector = tile_map_editor_plugins[plugin_index]->get_tabs();
+ for (int tab_index = 0; tab_index < tabs_vector.size(); tab_index++) {
+ tabs_bar->add_tab(tabs_vector[tab_index].panel->get_name());
+ tabs_data.push_back(tabs_vector[tab_index]);
+ tabs_plugins.push_back(tile_map_editor_plugins[plugin_index]);
+ }
}
- tabs->connect("tab_changed", callable_mp(this, &TileMapEditor::_tab_changed));
+ tabs_bar->connect("tab_changed", callable_mp(this, &TileMapEditor::_tab_changed));
// --- TileMap toolbar ---
tile_map_toolbar = memnew(HBoxContainer);
tile_map_toolbar->set_h_size_flags(SIZE_EXPAND_FILL);
// Tabs.
- tile_map_toolbar->add_child(tabs);
+ tile_map_toolbar->add_child(tabs_bar);
// Tabs toolbars.
- for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
- tile_map_editor_plugins[i]->get_toolbar()->hide();
- tile_map_toolbar->add_child(tile_map_editor_plugins[i]->get_toolbar());
+ for (unsigned int tab_index = 0; tab_index < tabs_data.size(); tab_index++) {
+ tabs_data[tab_index].toolbar->hide();
+ if (!tabs_data[tab_index].toolbar->get_parent()) {
+ tile_map_toolbar->add_child(tabs_data[tab_index].toolbar);
+ }
}
// Wide empty separation control.
@@ -3956,11 +4130,11 @@ TileMapEditor::TileMapEditor() {
missing_tileset_label->hide();
add_child(missing_tileset_label);
- for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
- add_child(tile_map_editor_plugins[i]);
- tile_map_editor_plugins[i]->set_h_size_flags(SIZE_EXPAND_FILL);
- tile_map_editor_plugins[i]->set_v_size_flags(SIZE_EXPAND_FILL);
- tile_map_editor_plugins[i]->set_visible(i == 0);
+ for (unsigned int tab_index = 0; tab_index < tabs_data.size(); tab_index++) {
+ add_child(tabs_data[tab_index].panel);
+ tabs_data[tab_index].panel->set_v_size_flags(SIZE_EXPAND_FILL);
+ tabs_data[tab_index].panel->set_visible(tab_index == 0);
+ tabs_data[tab_index].panel->set_h_size_flags(SIZE_EXPAND_FILL);
}
_tab_changed(0);
@@ -3970,4 +4144,7 @@ TileMapEditor::TileMapEditor() {
}
TileMapEditor::~TileMapEditor() {
+ for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
+ memdelete(tile_map_editor_plugins[i]);
+ }
}
diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h
index 5fbd9cada8..54bac160a3 100644
--- a/editor/plugins/tiles/tile_map_editor.h
+++ b/editor/plugins/tiles/tile_map_editor.h
@@ -33,17 +33,24 @@
#include "tile_atlas_view.h"
+#include "core/os/thread.h"
#include "core/typedefs.h"
#include "editor/editor_node.h"
#include "scene/2d/tile_map.h"
#include "scene/gui/box_container.h"
#include "scene/gui/tabs.h"
-class TileMapEditorPlugin : public VBoxContainer {
+class TileMapEditorPlugin : public Object {
public:
- virtual Control *get_toolbar() const {
- return memnew(Control);
+ struct TabData {
+ Control *toolbar;
+ Control *panel;
};
+
+ virtual Vector<TabData> get_tabs() const {
+ return Vector<TabData>();
+ };
+
virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) { return false; };
virtual void forward_canvas_draw_over_viewport(Control *p_overlay){};
virtual void tile_set_changed(){};
@@ -106,7 +113,7 @@ private:
Map<Vector2i, TileMapCell> drag_modified;
bool rmb_erasing = false;
- TileMapCell _pick_random_tile(const TileMapPattern *p_pattern);
+ TileMapCell _pick_random_tile(Ref<TileMapPattern> p_pattern);
Map<Vector2i, TileMapCell> _draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos);
Map<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell);
Map<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous);
@@ -115,20 +122,25 @@ private:
///// Selection system. /////
Set<Vector2i> tile_map_selection;
- TileMapPattern *tile_map_clipboard = memnew(TileMapPattern);
- TileMapPattern *selection_pattern = memnew(TileMapPattern);
+ Ref<TileMapPattern> tile_map_clipboard;
+ Ref<TileMapPattern> selection_pattern;
void _set_tile_map_selection(const TypedArray<Vector2i> &p_selection);
TypedArray<Vector2i> _get_tile_map_selection() const;
Set<TileMapCell> tile_set_selection;
void _update_selection_pattern_from_tilemap_selection();
- void _update_selection_pattern_from_tileset_selection();
+ void _update_selection_pattern_from_tileset_tiles_selection();
+ void _update_selection_pattern_from_tileset_pattern_selection();
void _update_tileset_selection_from_selection_pattern();
void _update_fix_selected_and_hovered();
void _fix_invalid_tiles_in_tile_map_selection();
- ///// Bottom panel. ////.
+ ///// Bottom panel common ////
+ void _tab_changed();
+
+ ///// Bottom panel tiles ////
+ VBoxContainer *tiles_bottom_panel;
Label *missing_source_label;
Label *invalid_source_label;
@@ -137,7 +149,7 @@ private:
Ref<Texture2D> missing_atlas_texture_icon;
void _update_tile_set_sources_list();
- void _update_bottom_panel();
+ void _update_source_display();
// Atlas sources.
TileMapCell hovered_tile;
@@ -167,15 +179,26 @@ private:
void _scenes_list_multi_selected(int p_index, bool p_selected);
void _scenes_list_nothing_selected();
+ ///// Bottom panel patterns ////
+ VBoxContainer *patterns_bottom_panel;
+ ItemList *patterns_item_list;
+ Label *patterns_help_label;
+ void _patterns_item_list_gui_input(const Ref<InputEvent> &p_event);
+ void _pattern_preview_done(Ref<TileMapPattern> p_pattern, Ref<Texture2D> p_texture);
+ bool select_last_pattern = false;
+ void _update_patterns_list();
+
+ // General
+ void _update_theme();
+
// Update callback
virtual void tile_set_changed() override;
protected:
- void _notification(int p_what);
static void _bind_methods();
public:
- virtual Control *get_toolbar() const override;
+ virtual Vector<TabData> get_tabs() const override;
virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override;
virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override;
@@ -205,6 +228,9 @@ private:
void _update_toolbar();
+ // Main vbox.
+ VBoxContainer *main_vbox_container;
+
// TileMap editing.
enum DragType {
DRAG_TYPE_NONE = 0,
@@ -281,16 +307,13 @@ private:
void _update_terrains_cache();
void _update_terrains_tree();
void _update_tiles_list();
+ void _update_theme();
// Update callback
virtual void tile_set_changed() override;
-protected:
- void _notification(int p_what);
- // static void _bind_methods();
-
public:
- virtual Control *get_toolbar() const override;
+ virtual Vector<TabData> get_tabs() const override;
virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override;
//virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override;
@@ -328,7 +351,9 @@ private:
// Bottom panel.
Label *missing_tileset_label;
- Tabs *tabs;
+ Tabs *tabs_bar;
+ LocalVector<TileMapEditorPlugin::TabData> tabs_data;
+ LocalVector<TileMapEditorPlugin *> tabs_plugins;
void _update_bottom_panel();
// TileMap.
diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp
index 6d0d184942..44f5ba29de 100644
--- a/editor/plugins/tiles/tile_set_editor.cpp
+++ b/editor/plugins/tiles/tile_set_editor.cpp
@@ -156,9 +156,9 @@ void TileSetEditor::_update_sources_list(int force_selected_id) {
texture = atlas_source->get_texture();
if (item_text.is_empty()) {
if (texture.is_valid()) {
- item_text = vformat("%s (id:%d)", texture->get_path().get_file(), source_id);
+ item_text = vformat("%s (ID:%d)", texture->get_path().get_file(), source_id);
} else {
- item_text = vformat(TTR("No Texture Atlas Source (id:%d)"), source_id);
+ item_text = vformat(TTR("No Texture Atlas Source (ID:%d)"), source_id);
}
}
}
@@ -168,13 +168,13 @@ void TileSetEditor::_update_sources_list(int force_selected_id) {
if (scene_collection_source) {
texture = get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons"));
if (item_text.is_empty()) {
- item_text = vformat(TTR("Scene Collection Source (id:%d)"), source_id);
+ item_text = vformat(TTR("Scene Collection Source (ID:%d)"), source_id);
}
}
// Use default if not valid.
if (item_text.is_empty()) {
- item_text = vformat(TTR("Unknown Type Source (id:%d)"), source_id);
+ item_text = vformat(TTR("Unknown Type Source (ID:%d)"), source_id);
}
if (!texture.is_valid()) {
texture = missing_texture_texture;
@@ -327,6 +327,7 @@ void TileSetEditor::_notification(int p_what) {
tile_set->set_edited(true);
}
_update_sources_list();
+ _update_patterns_list();
tile_set_changed_needs_update = false;
}
break;
@@ -335,10 +336,56 @@ void TileSetEditor::_notification(int p_what) {
}
}
+void TileSetEditor::_patterns_item_list_gui_input(const Ref<InputEvent> &p_event) {
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ if (ED_IS_SHORTCUT("tiles_editor/delete", p_event) && p_event->is_pressed() && !p_event->is_echo()) {
+ Vector<int> selected = patterns_item_list->get_selected_items();
+ undo_redo->create_action(TTR("Remove TileSet patterns"));
+ for (int i = 0; i < selected.size(); i++) {
+ int pattern_index = selected[i];
+ undo_redo->add_do_method(*tile_set, "remove_pattern", pattern_index);
+ undo_redo->add_undo_method(*tile_set, "add_pattern", tile_set->get_pattern(pattern_index), pattern_index);
+ }
+ undo_redo->commit_action();
+ patterns_item_list->accept_event();
+ }
+}
+
+void TileSetEditor::_pattern_preview_done(Ref<TileMapPattern> p_pattern, Ref<Texture2D> p_texture) {
+ // TODO optimize ?
+ for (int i = 0; i < patterns_item_list->get_item_count(); i++) {
+ if (patterns_item_list->get_item_metadata(i) == p_pattern) {
+ patterns_item_list->set_item_icon(i, p_texture);
+ break;
+ }
+ }
+}
+
+void TileSetEditor::_update_patterns_list() {
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ // Recreate the items.
+ patterns_item_list->clear();
+ for (int i = 0; i < tile_set->get_patterns_count(); i++) {
+ int id = patterns_item_list->add_item("");
+ patterns_item_list->set_item_metadata(id, tile_set->get_pattern(i));
+ TilesEditor::get_singleton()->queue_pattern_preview(tile_set, tile_set->get_pattern(i), callable_mp(this, &TileSetEditor::_pattern_preview_done));
+ }
+
+ // Update the label visibility.
+ patterns_help_label->set_visible(patterns_item_list->get_item_count() == 0);
+}
+
void TileSetEditor::_tile_set_changed() {
tile_set_changed_needs_update = true;
}
+void TileSetEditor::_tab_changed(int p_tab_changed) {
+ split_container->set_visible(p_tab_changed == 0);
+ patterns_item_list->set_visible(p_tab_changed == 1);
+}
+
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);
@@ -582,6 +629,7 @@ void TileSetEditor::edit(Ref<TileSet> p_tile_set) {
if (tile_set.is_valid()) {
tile_set->connect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed));
_update_sources_list();
+ _update_patterns_list();
}
tile_set_atlas_source_editor->hide();
@@ -594,8 +642,20 @@ TileSetEditor::TileSetEditor() {
set_process_internal(true);
+ // Tabs.
+ tabs_bar = memnew(Tabs);
+ tabs_bar->set_clip_tabs(false);
+ tabs_bar->add_tab(TTR("Tiles"));
+ tabs_bar->add_tab(TTR("Patterns"));
+ tabs_bar->connect("tab_changed", callable_mp(this, &TileSetEditor::_tab_changed));
+
+ tile_set_toolbar = memnew(HBoxContainer);
+ tile_set_toolbar->set_h_size_flags(SIZE_EXPAND_FILL);
+ tile_set_toolbar->add_child(tabs_bar);
+
+ //// Tiles ////
// Split container.
- HSplitContainer *split_container = memnew(HSplitContainer);
+ split_container = memnew(HSplitContainer);
split_container->set_name(TTR("Tiles"));
split_container->set_h_size_flags(SIZE_EXPAND_FILL);
split_container->set_v_size_flags(SIZE_EXPAND_FILL);
@@ -681,6 +741,24 @@ TileSetEditor::TileSetEditor() {
split_container_right_side->add_child(tile_set_scenes_collection_source_editor);
tile_set_scenes_collection_source_editor->hide();
+ //// Patterns ////
+ int thumbnail_size = 64;
+ patterns_item_list = memnew(ItemList);
+ patterns_item_list->set_max_columns(0);
+ patterns_item_list->set_icon_mode(ItemList::ICON_MODE_TOP);
+ patterns_item_list->set_fixed_column_width(thumbnail_size * 3 / 2);
+ patterns_item_list->set_max_text_lines(2);
+ patterns_item_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size));
+ patterns_item_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ patterns_item_list->connect("gui_input", callable_mp(this, &TileSetEditor::_patterns_item_list_gui_input));
+ add_child(patterns_item_list);
+ patterns_item_list->hide();
+
+ patterns_help_label = memnew(Label);
+ patterns_help_label->set_text(TTR("Add new patterns in the TileMap editing mode."));
+ patterns_help_label->set_anchors_and_offsets_preset(Control::PRESET_CENTER);
+ patterns_item_list->add_child(patterns_help_label);
+
// 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 fe854b2281..1f26560588 100644
--- a/editor/plugins/tiles/tile_set_editor.h
+++ b/editor/plugins/tiles/tile_set_editor.h
@@ -46,7 +46,13 @@ class TileSetEditor : public VBoxContainer {
private:
Ref<TileSet> tile_set;
bool tile_set_changed_needs_update = false;
+ HSplitContainer *split_container;
+ // Tabs.
+ HBoxContainer *tile_set_toolbar;
+ Tabs *tabs_bar;
+
+ // Tiles.
Label *no_source_selected_label;
TileSetAtlasSourceEditor *tile_set_atlas_source_editor;
TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor;
@@ -69,7 +75,16 @@ private:
AtlasMergingDialog *atlas_merging_dialog;
TileProxiesManagerDialog *tile_proxies_manager_dialog;
+ // Patterns.
+ ItemList *patterns_item_list;
+ Label *patterns_help_label;
+ void _patterns_item_list_gui_input(const Ref<InputEvent> &p_event);
+ void _pattern_preview_done(Ref<TileMapPattern> p_pattern, Ref<Texture2D> p_texture);
+ bool select_last_pattern = false;
+ void _update_patterns_list();
+
void _tile_set_changed();
+ void _tab_changed(int p_tab_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);
@@ -82,6 +97,8 @@ public:
_FORCE_INLINE_ static TileSetEditor *get_singleton() { return singleton; }
void edit(Ref<TileSet> p_tile_set);
+ Control *get_toolbar() { return tile_set_toolbar; };
+
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;
diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp
index d0d01a8d49..cc1b80fa8e 100644
--- a/editor/plugins/tiles/tiles_editor_plugin.cpp
+++ b/editor/plugins/tiles/tiles_editor_plugin.cpp
@@ -30,17 +30,18 @@
#include "tiles_editor_plugin.h"
+#include "core/os/mutex.h"
+
#include "editor/editor_node.h"
#include "editor/editor_scale.h"
#include "editor/plugins/canvas_item_editor_plugin.h"
#include "scene/2d/tile_map.h"
-#include "scene/resources/tile_set.h"
-
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/control.h"
#include "scene/gui/separator.h"
+#include "scene/resources/tile_set.h"
#include "tile_set_editor.h"
@@ -65,6 +66,99 @@ void TilesEditor::_notification(int p_what) {
}
}
+void TilesEditor::_pattern_preview_done(const Variant &p_udata) {
+ pattern_preview_done.set();
+}
+
+void TilesEditor::_thread_func(void *ud) {
+ TilesEditor *te = (TilesEditor *)ud;
+ te->_thread();
+}
+
+void TilesEditor::_thread() {
+ pattern_thread_exited.clear();
+ while (!pattern_thread_exit.is_set()) {
+ pattern_preview_sem.wait();
+
+ pattern_preview_mutex.lock();
+ if (pattern_preview_queue.size()) {
+ QueueItem item = pattern_preview_queue.front()->get();
+ pattern_preview_queue.pop_front();
+ pattern_preview_mutex.unlock();
+
+ int thumbnail_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size");
+ thumbnail_size *= EDSCALE;
+ Vector2 thumbnail_size2 = Vector2(thumbnail_size, thumbnail_size);
+
+ if (item.pattern.is_valid() && !item.pattern->is_empty()) {
+ // Generate the pattern preview
+ SubViewport *viewport = memnew(SubViewport);
+ viewport->set_size(thumbnail_size2);
+ viewport->set_disable_input(true);
+ viewport->set_transparent_background(true);
+ viewport->set_update_mode(SubViewport::UPDATE_ONCE);
+
+ TileMap *tile_map = memnew(TileMap);
+ tile_map->set_tileset(item.tile_set);
+ tile_map->set_pattern(0, Vector2(), item.pattern);
+ viewport->add_child(tile_map);
+
+ TypedArray<Vector2i> used_cells = tile_map->get_used_cells(0);
+
+ Rect2 encompassing_rect = Rect2();
+ encompassing_rect.set_position(tile_map->map_to_world(used_cells[0]));
+ for (int i = 0; i < used_cells.size(); i++) {
+ Vector2i cell = used_cells[i];
+ Vector2 world_pos = tile_map->map_to_world(cell);
+ encompassing_rect.expand_to(world_pos);
+
+ // Texture.
+ Ref<TileSetAtlasSource> atlas_source = tile_set->get_source(tile_map->get_cell_source_id(0, cell));
+ if (atlas_source.is_valid()) {
+ Vector2i coords = tile_map->get_cell_atlas_coords(0, cell);
+ int alternative = tile_map->get_cell_alternative_tile(0, cell);
+
+ Vector2 center = world_pos - atlas_source->get_tile_effective_texture_offset(coords, alternative);
+ encompassing_rect.expand_to(center - atlas_source->get_tile_texture_region(coords).size / 2);
+ encompassing_rect.expand_to(center + atlas_source->get_tile_texture_region(coords).size / 2);
+ }
+ }
+
+ Vector2 scale = thumbnail_size2 / MAX(encompassing_rect.size.x, encompassing_rect.size.y);
+ tile_map->set_scale(scale);
+ tile_map->set_position(-(scale * encompassing_rect.get_center()) + thumbnail_size2 / 2);
+
+ // Add the viewport at the lasst moment to avoid rendering too early.
+ EditorNode::get_singleton()->add_child(viewport);
+
+ pattern_preview_done.clear();
+ RS::get_singleton()->request_frame_drawn_callback(const_cast<TilesEditor *>(this), "_pattern_preview_done", Variant());
+
+ while (!pattern_preview_done.is_set()) {
+ OS::get_singleton()->delay_usec(10);
+ }
+
+ Ref<Image> image = viewport->get_texture()->get_image();
+ Ref<ImageTexture> image_texture;
+ image_texture.instantiate();
+ image_texture->create_from_image(image);
+
+ // Find the index for the given pattern. TODO: optimize.
+ Variant args[] = { item.pattern, image_texture };
+ const Variant *args_ptr[] = { &args[0], &args[1] };
+ Variant r;
+ Callable::CallError error;
+ item.callback.call(args_ptr, 2, r, error);
+
+ viewport->queue_delete();
+ } else {
+ pattern_preview_mutex.unlock();
+ }
+ }
+ }
+ pattern_thread_exited.set();
+}
+
void TilesEditor::_tile_map_changed() {
tile_map_changed_needs_update = true;
}
@@ -83,6 +177,7 @@ void TilesEditor::_update_editors() {
// Set editors visibility.
tilemap_toolbar->set_visible(!tileset_tilemap_switch_button->is_pressed());
tilemap_editor->set_visible(!tileset_tilemap_switch_button->is_pressed());
+ tileset_toolbar->set_visible(tileset_tilemap_switch_button->is_pressed());
tileset_editor->set_visible(tileset_tilemap_switch_button->is_pressed());
// Enable/disable the switch button.
@@ -150,6 +245,16 @@ void TilesEditor::synchronize_atlas_view(Object *p_current) {
}
}
+void TilesEditor::queue_pattern_preview(Ref<TileSet> p_tile_set, Ref<TileMapPattern> p_pattern, Callable p_callback) {
+ ERR_FAIL_COND(!p_tile_set.is_valid());
+ ERR_FAIL_COND(!p_pattern.is_valid());
+ {
+ MutexLock lock(pattern_preview_mutex);
+ pattern_preview_queue.push_back({ p_tile_set, p_pattern, p_callback });
+ }
+ pattern_preview_sem.post();
+}
+
void TilesEditor::edit(Object *p_object) {
// Disconnect to changes.
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
@@ -192,6 +297,7 @@ void TilesEditor::edit(Object *p_object) {
}
void TilesEditor::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_pattern_preview_done", "pattern"), &TilesEditor::_pattern_preview_done);
}
TilesEditor::TilesEditor(EditorNode *p_editor) {
@@ -229,12 +335,27 @@ TilesEditor::TilesEditor(EditorNode *p_editor) {
tileset_editor->hide();
add_child(tileset_editor);
+ tileset_toolbar = tileset_editor->get_toolbar();
+ toolbar->add_child(tileset_toolbar);
+
+ // Pattern preview generation thread.
+ pattern_preview_thread.start(_thread_func, this);
+
// Initialization.
_update_switch_button();
_update_editors();
}
TilesEditor::~TilesEditor() {
+ if (pattern_preview_thread.is_started()) {
+ pattern_thread_exit.set();
+ pattern_preview_sem.post();
+ while (!pattern_thread_exited.is_set()) {
+ OS::get_singleton()->delay_usec(10000);
+ RenderingServer::get_singleton()->sync(); //sync pending stuff, as thread may be blocked on visual server
+ }
+ pattern_preview_thread.wait_to_finish();
+ }
}
///////////////////////////////////////////////////////////////
diff --git a/editor/plugins/tiles/tiles_editor_plugin.h b/editor/plugins/tiles/tiles_editor_plugin.h
index f976d68938..c77fd76d1c 100644
--- a/editor/plugins/tiles/tiles_editor_plugin.h
+++ b/editor/plugins/tiles/tiles_editor_plugin.h
@@ -53,6 +53,7 @@ private:
Control *tilemap_toolbar;
TileMapEditor *tilemap_editor;
+ Control *tileset_toolbar;
TileSetEditor *tileset_editor;
void _update_switch_button();
@@ -63,6 +64,23 @@ private:
float atlas_view_zoom = 1.0;
Vector2 atlas_view_scroll = Vector2();
+ // Patterns preview generation.
+ struct QueueItem {
+ Ref<TileSet> tile_set;
+ Ref<TileMapPattern> pattern;
+ Callable callback;
+ };
+ List<QueueItem> pattern_preview_queue;
+ Mutex pattern_preview_mutex;
+ Semaphore pattern_preview_sem;
+ Thread pattern_preview_thread;
+ SafeFlag pattern_thread_exit;
+ SafeFlag pattern_thread_exited;
+ mutable SafeFlag pattern_preview_done;
+ void _pattern_preview_done(const Variant &p_udata);
+ static void _thread_func(void *ud);
+ void _thread();
+
void _tile_map_changed();
protected:
@@ -82,6 +100,9 @@ public:
void set_atlas_view_transform(float p_zoom, Vector2 p_scroll);
void synchronize_atlas_view(Object *p_current);
+ // Pattern preview API.
+ void queue_pattern_preview(Ref<TileSet> p_tile_set, Ref<TileMapPattern> p_pattern, Callable p_callback);
+
void edit(Object *p_object);
TilesEditor(EditorNode *p_editor);
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index c2f150ce00..b546eaefa6 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -34,98 +34,6 @@
#include "servers/navigation_server_2d.h"
-void TileMapPattern::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) {
- ERR_FAIL_COND_MSG(p_coords.x < 0 || p_coords.y < 0, vformat("Cannot set cell with negative coords in a TileMapPattern. Wrong coords: %s", p_coords));
-
- size = size.max(p_coords + Vector2i(1, 1));
- pattern[p_coords] = TileMapCell(p_source_id, p_atlas_coords, p_alternative_tile);
-}
-
-bool TileMapPattern::has_cell(const Vector2i &p_coords) const {
- return pattern.has(p_coords);
-}
-
-void TileMapPattern::remove_cell(const Vector2i &p_coords, bool p_update_size) {
- ERR_FAIL_COND(!pattern.has(p_coords));
-
- pattern.erase(p_coords);
- if (p_update_size) {
- size = Vector2i();
- for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
- size = size.max(E.key + Vector2i(1, 1));
- }
- }
-}
-
-int TileMapPattern::get_cell_source_id(const Vector2i &p_coords) const {
- ERR_FAIL_COND_V(!pattern.has(p_coords), TileSet::INVALID_SOURCE);
-
- return pattern[p_coords].source_id;
-}
-
-Vector2i TileMapPattern::get_cell_atlas_coords(const Vector2i &p_coords) const {
- ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_ATLAS_COORDS);
-
- return pattern[p_coords].get_atlas_coords();
-}
-
-int TileMapPattern::get_cell_alternative_tile(const Vector2i &p_coords) const {
- ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_TILE_ALTERNATIVE);
-
- return pattern[p_coords].alternative_tile;
-}
-
-TypedArray<Vector2i> TileMapPattern::get_used_cells() const {
- // Returns the cells used in the tilemap.
- TypedArray<Vector2i> a;
- a.resize(pattern.size());
- int i = 0;
- for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
- Vector2i p(E.key.x, E.key.y);
- a[i++] = p;
- }
-
- return a;
-}
-
-Vector2i TileMapPattern::get_size() const {
- return size;
-}
-
-void TileMapPattern::set_size(const Vector2i &p_size) {
- for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
- Vector2i coords = E.key;
- if (p_size.x <= coords.x || p_size.y <= coords.y) {
- ERR_FAIL_MSG(vformat("Cannot set pattern size to %s, it contains a tile at %s. Size can only be increased.", p_size, coords));
- };
- }
-
- size = p_size;
-}
-
-bool TileMapPattern::is_empty() const {
- return pattern.is_empty();
-};
-
-void TileMapPattern::clear() {
- size = Vector2i();
- pattern.clear();
-};
-
-void TileMapPattern::_bind_methods() {
- ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapPattern::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE));
- ClassDB::bind_method(D_METHOD("has_cell", "coords"), &TileMapPattern::has_cell);
- ClassDB::bind_method(D_METHOD("remove_cell", "coords"), &TileMapPattern::remove_cell);
- ClassDB::bind_method(D_METHOD("get_cell_source_id", "coords"), &TileMapPattern::get_cell_source_id);
- ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "coords"), &TileMapPattern::get_cell_atlas_coords);
- ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "coords"), &TileMapPattern::get_cell_alternative_tile);
-
- ClassDB::bind_method(D_METHOD("get_used_cells"), &TileMapPattern::get_used_cells);
- ClassDB::bind_method(D_METHOD("get_size"), &TileMapPattern::get_size);
- ClassDB::bind_method(D_METHOD("set_size", "size"), &TileMapPattern::set_size);
- ClassDB::bind_method(D_METHOD("is_empty"), &TileMapPattern::is_empty);
-}
-
Vector2i TileMap::transform_coords_layout(Vector2i p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout) {
// Transform to stacked layout.
Vector2i output = p_coords;
@@ -1788,11 +1696,12 @@ int TileMap::get_cell_alternative_tile(int p_layer, const Vector2i &p_coords, bo
return E->get().alternative_tile;
}
-TileMapPattern *TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array) {
+Ref<TileMapPattern> TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array) {
ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr);
ERR_FAIL_COND_V(!tile_set.is_valid(), nullptr);
- TileMapPattern *output = memnew(TileMapPattern);
+ Ref<TileMapPattern> output;
+ output.instantiate();
if (p_coords_array.is_empty()) {
return output;
}
@@ -1841,7 +1750,7 @@ TileMapPattern *TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coords_
return output;
}
-Vector2i TileMap::map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_in_pattern, const TileMapPattern *p_pattern) {
+Vector2i TileMap::map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_in_pattern, Ref<TileMapPattern> p_pattern) {
ERR_FAIL_COND_V(!p_pattern->has_cell(p_coords_in_pattern), Vector2i());
Vector2i output = p_position_in_tilemap + p_coords_in_pattern;
@@ -1864,7 +1773,7 @@ Vector2i TileMap::map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_
return output;
}
-void TileMap::set_pattern(int p_layer, Vector2i p_position, const TileMapPattern *p_pattern) {
+void TileMap::set_pattern(int p_layer, Vector2i p_position, const Ref<TileMapPattern> p_pattern) {
ERR_FAIL_INDEX(p_layer, (int)layers.size());
ERR_FAIL_COND(!tile_set.is_valid());
@@ -3076,6 +2985,10 @@ void TileMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_coords_for_body_rid", "body"), &TileMap::get_coords_for_body_rid);
+ ClassDB::bind_method(D_METHOD("get_pattern", "layer", "coords_array"), &TileMap::get_pattern);
+ ClassDB::bind_method(D_METHOD("map_pattern", "position_in_tilemap", "coords_in_pattern", "pattern"), &TileMap::map_pattern);
+ ClassDB::bind_method(D_METHOD("set_pattern", "layer", "position", "pattern"), &TileMap::set_pattern);
+
ClassDB::bind_method(D_METHOD("fix_invalid_tiles"), &TileMap::fix_invalid_tiles);
ClassDB::bind_method(D_METHOD("clear_layer", "layer"), &TileMap::clear_layer);
ClassDB::bind_method(D_METHOD("clear"), &TileMap::clear);
@@ -3092,7 +3005,7 @@ void TileMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("_update_dirty_quadrants"), &TileMap::_update_dirty_quadrants);
- ClassDB::bind_method(D_METHOD("_set_tile_data", "layer"), &TileMap::_set_tile_data);
+ ClassDB::bind_method(D_METHOD("_set_tile_data", "layer", "data"), &TileMap::_set_tile_data);
ClassDB::bind_method(D_METHOD("_get_tile_data", "layer"), &TileMap::_get_tile_data);
ClassDB::bind_method(D_METHOD("_tile_set_changed_deferred_update"), &TileMap::_tile_set_changed_deferred_update);
diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h
index e5809deabb..e1f38a314c 100644
--- a/scene/2d/tile_map.h
+++ b/scene/2d/tile_map.h
@@ -37,51 +37,6 @@
class TileSetAtlasSource;
-union TileMapCell {
- struct {
- int32_t source_id : 16;
- int16_t coord_x : 16;
- int16_t coord_y : 16;
- int32_t alternative_tile : 16;
- };
-
- uint64_t _u64t;
- TileMapCell(int p_source_id = -1, Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE) {
- source_id = p_source_id;
- set_atlas_coords(p_atlas_coords);
- alternative_tile = p_alternative_tile;
- }
-
- Vector2i get_atlas_coords() const {
- return Vector2i(coord_x, coord_y);
- }
-
- void set_atlas_coords(const Vector2i &r_coords) {
- coord_x = r_coords.x;
- coord_y = r_coords.y;
- }
-
- bool operator<(const TileMapCell &p_other) const {
- if (source_id == p_other.source_id) {
- if (coord_x == p_other.coord_x) {
- if (coord_y == p_other.coord_y) {
- return alternative_tile < p_other.alternative_tile;
- } else {
- return coord_y < p_other.coord_y;
- }
- } else {
- return coord_x < p_other.coord_x;
- }
- } else {
- return source_id < p_other.source_id;
- }
- }
-
- bool operator!=(const TileMapCell &p_other) const {
- return !(source_id == p_other.source_id && coord_x == p_other.coord_x && coord_y == p_other.coord_y && alternative_tile == p_other.alternative_tile);
- }
-};
-
struct TileMapQuadrant {
struct CoordsWorldComparator {
_ALWAYS_INLINE_ bool operator()(const Vector2i &p_a, const Vector2i &p_b) const {
@@ -150,32 +105,6 @@ struct TileMapQuadrant {
}
};
-class TileMapPattern : public Object {
- GDCLASS(TileMapPattern, Object);
-
- Vector2i size;
- Map<Vector2i, TileMapCell> pattern;
-
-protected:
- static void _bind_methods();
-
-public:
- void set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile = 0);
- bool has_cell(const Vector2i &p_coords) const;
- void remove_cell(const Vector2i &p_coords, bool p_update_size = true);
- int get_cell_source_id(const Vector2i &p_coords) const;
- Vector2i get_cell_atlas_coords(const Vector2i &p_coords) const;
- int get_cell_alternative_tile(const Vector2i &p_coords) const;
-
- TypedArray<Vector2i> get_used_cells() const;
-
- Vector2i get_size() const;
- void set_size(const Vector2i &p_size);
- bool is_empty() const;
-
- void clear();
-};
-
class TileMap : public Node2D {
GDCLASS(TileMap, Node2D);
@@ -349,9 +278,9 @@ public:
Vector2i get_cell_atlas_coords(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
int get_cell_alternative_tile(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
- TileMapPattern *get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array);
- Vector2i map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_in_pattern, const TileMapPattern *p_pattern);
- void set_pattern(int p_layer, Vector2i p_position, const TileMapPattern *p_pattern);
+ Ref<TileMapPattern> get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array);
+ Vector2i map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_in_pattern, Ref<TileMapPattern> p_pattern);
+ void set_pattern(int p_layer, Vector2i p_position, const Ref<TileMapPattern> p_pattern);
// Not exposed to users
TileMapCell get_cell(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 38da40a402..973074397b 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -740,7 +740,7 @@ bool Control::has_point(const Point2 &p_point) const {
return Rect2(Point2(), get_size()).has_point(p_point);
}
-void Control::set_drag_forwarding(Node *p_target) {
+void Control::set_drag_forwarding(Object *p_target) {
if (p_target) {
data.drag_owner = p_target->get_instance_id();
} else {
diff --git a/scene/gui/control.h b/scene/gui/control.h
index be692b6a0c..b551a2e299 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -355,7 +355,7 @@ public:
virtual Size2 get_minimum_size() const;
virtual Size2 get_combined_minimum_size() const;
virtual bool has_point(const Point2 &p_point) const;
- virtual void set_drag_forwarding(Node *p_target);
+ virtual void set_drag_forwarding(Object *p_target);
virtual Variant get_drag_data(const Point2 &p_point);
virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const;
virtual void drop_data(const Point2 &p_point, const Variant &p_data);
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index f293c8a8e6..61c25ae9db 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -685,6 +685,7 @@ void register_scene_types() {
GDREGISTER_VIRTUAL_CLASS(TileSetSource);
GDREGISTER_CLASS(TileSetAtlasSource);
GDREGISTER_CLASS(TileSetScenesCollectionSource);
+ GDREGISTER_CLASS(TileMapPattern);
GDREGISTER_CLASS(TileData);
GDREGISTER_CLASS(TileMap);
GDREGISTER_CLASS(ParallaxBackground);
diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp
index b988b2ab3e..a45b6b8eb6 100644
--- a/scene/resources/tile_set.cpp
+++ b/scene/resources/tile_set.cpp
@@ -31,6 +31,7 @@
#include "tile_set.h"
#include "core/core_string_names.h"
+#include "core/io/marshalls.h"
#include "core/math/geometry_2d.h"
#include "core/templates/local_vector.h"
@@ -39,6 +40,189 @@
#include "scene/resources/convex_polygon_shape_2d.h"
#include "servers/navigation_server_2d.h"
+/////////////////////////////// TileMapPattern //////////////////////////////////////
+
+void TileMapPattern::_set_tile_data(const Vector<int> &p_data) {
+ int c = p_data.size();
+ const int *r = p_data.ptr();
+
+ int offset = 3;
+ ERR_FAIL_COND_MSG(c % offset != 0, "Corrupted tile data.");
+
+ clear();
+
+ for (int i = 0; i < c; i += offset) {
+ const uint8_t *ptr = (const uint8_t *)&r[i];
+ uint8_t local[12];
+ for (int j = 0; j < 12; j++) {
+ local[j] = ptr[j];
+ }
+
+#ifdef BIG_ENDIAN_ENABLED
+ SWAP(local[0], local[3]);
+ SWAP(local[1], local[2]);
+ SWAP(local[4], local[7]);
+ SWAP(local[5], local[6]);
+ SWAP(local[8], local[11]);
+ SWAP(local[9], local[10]);
+#endif
+
+ int16_t x = decode_uint16(&local[0]);
+ int16_t y = decode_uint16(&local[2]);
+ uint16_t source_id = decode_uint16(&local[4]);
+ uint16_t atlas_coords_x = decode_uint16(&local[6]);
+ uint16_t atlas_coords_y = decode_uint16(&local[8]);
+ uint16_t alternative_tile = decode_uint16(&local[10]);
+ set_cell(Vector2i(x, y), source_id, Vector2i(atlas_coords_x, atlas_coords_y), alternative_tile);
+ }
+ emit_signal(SNAME("changed"));
+}
+
+Vector<int> TileMapPattern::_get_tile_data() const {
+ // Export tile data to raw format
+ Vector<int> data;
+ data.resize(pattern.size() * 3);
+ int *w = data.ptrw();
+
+ // Save in highest format
+
+ int idx = 0;
+ for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
+ uint8_t *ptr = (uint8_t *)&w[idx];
+ encode_uint16((int16_t)(E.key.x), &ptr[0]);
+ encode_uint16((int16_t)(E.key.y), &ptr[2]);
+ encode_uint16(E.value.source_id, &ptr[4]);
+ encode_uint16(E.value.coord_x, &ptr[6]);
+ encode_uint16(E.value.coord_y, &ptr[8]);
+ encode_uint16(E.value.alternative_tile, &ptr[10]);
+ idx += 3;
+ }
+
+ return data;
+}
+
+void TileMapPattern::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) {
+ ERR_FAIL_COND_MSG(p_coords.x < 0 || p_coords.y < 0, vformat("Cannot set cell with negative coords in a TileMapPattern. Wrong coords: %s", p_coords));
+
+ size = size.max(p_coords + Vector2i(1, 1));
+ pattern[p_coords] = TileMapCell(p_source_id, p_atlas_coords, p_alternative_tile);
+ emit_changed();
+}
+
+bool TileMapPattern::has_cell(const Vector2i &p_coords) const {
+ return pattern.has(p_coords);
+}
+
+void TileMapPattern::remove_cell(const Vector2i &p_coords, bool p_update_size) {
+ ERR_FAIL_COND(!pattern.has(p_coords));
+
+ pattern.erase(p_coords);
+ if (p_update_size) {
+ size = Vector2i();
+ for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
+ size = size.max(E.key + Vector2i(1, 1));
+ }
+ }
+ emit_changed();
+}
+
+int TileMapPattern::get_cell_source_id(const Vector2i &p_coords) const {
+ ERR_FAIL_COND_V(!pattern.has(p_coords), TileSet::INVALID_SOURCE);
+
+ return pattern[p_coords].source_id;
+}
+
+Vector2i TileMapPattern::get_cell_atlas_coords(const Vector2i &p_coords) const {
+ ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_ATLAS_COORDS);
+
+ return pattern[p_coords].get_atlas_coords();
+}
+
+int TileMapPattern::get_cell_alternative_tile(const Vector2i &p_coords) const {
+ ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_TILE_ALTERNATIVE);
+
+ return pattern[p_coords].alternative_tile;
+}
+
+TypedArray<Vector2i> TileMapPattern::get_used_cells() const {
+ // Returns the cells used in the tilemap.
+ TypedArray<Vector2i> a;
+ a.resize(pattern.size());
+ int i = 0;
+ for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
+ Vector2i p(E.key.x, E.key.y);
+ a[i++] = p;
+ }
+
+ return a;
+}
+
+Vector2i TileMapPattern::get_size() const {
+ return size;
+}
+
+void TileMapPattern::set_size(const Vector2i &p_size) {
+ for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
+ Vector2i coords = E.key;
+ if (p_size.x <= coords.x || p_size.y <= coords.y) {
+ ERR_FAIL_MSG(vformat("Cannot set pattern size to %s, it contains a tile at %s. Size can only be increased.", p_size, coords));
+ };
+ }
+
+ size = p_size;
+ emit_changed();
+}
+
+bool TileMapPattern::is_empty() const {
+ return pattern.is_empty();
+};
+
+void TileMapPattern::clear() {
+ size = Vector2i();
+ pattern.clear();
+ emit_changed();
+};
+
+bool TileMapPattern::_set(const StringName &p_name, const Variant &p_value) {
+ if (p_name == "tile_data") {
+ if (p_value.is_array()) {
+ _set_tile_data(p_value);
+ return true;
+ }
+ return false;
+ }
+ return false;
+}
+
+bool TileMapPattern::_get(const StringName &p_name, Variant &r_ret) const {
+ if (p_name == "tile_data") {
+ r_ret = _get_tile_data();
+ return true;
+ }
+ return false;
+}
+
+void TileMapPattern::_get_property_list(List<PropertyInfo> *p_list) const {
+ p_list->push_back(PropertyInfo(Variant::OBJECT, "tile_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
+}
+
+void TileMapPattern::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_set_tile_data", "data"), &TileMapPattern::_set_tile_data);
+ ClassDB::bind_method(D_METHOD("_get_tile_data"), &TileMapPattern::_get_tile_data);
+
+ ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapPattern::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE));
+ ClassDB::bind_method(D_METHOD("has_cell", "coords"), &TileMapPattern::has_cell);
+ ClassDB::bind_method(D_METHOD("remove_cell", "coords"), &TileMapPattern::remove_cell);
+ ClassDB::bind_method(D_METHOD("get_cell_source_id", "coords"), &TileMapPattern::get_cell_source_id);
+ ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "coords"), &TileMapPattern::get_cell_atlas_coords);
+ ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "coords"), &TileMapPattern::get_cell_alternative_tile);
+
+ ClassDB::bind_method(D_METHOD("get_used_cells"), &TileMapPattern::get_used_cells);
+ ClassDB::bind_method(D_METHOD("get_size"), &TileMapPattern::get_size);
+ ClassDB::bind_method(D_METHOD("set_size", "size"), &TileMapPattern::set_size);
+ ClassDB::bind_method(D_METHOD("is_empty"), &TileMapPattern::is_empty);
+}
+
/////////////////////////////// TileSet //////////////////////////////////////
const int TileSet::INVALID_SOURCE = -1;
@@ -982,6 +1166,36 @@ void TileSet::clear_tile_proxies() {
emit_changed();
}
+int TileSet::add_pattern(Ref<TileMapPattern> p_pattern, int p_index) {
+ ERR_FAIL_COND_V(!p_pattern.is_valid(), -1);
+ ERR_FAIL_COND_V_MSG(p_pattern->is_empty(), -1, "Cannot add an empty pattern to the TileSet.");
+ for (unsigned int i = 0; i < patterns.size(); i++) {
+ ERR_FAIL_COND_V_MSG(patterns[i] == p_pattern, -1, "TileSet has already this pattern.");
+ }
+ ERR_FAIL_COND_V(p_index > (int)patterns.size(), -1);
+ if (p_index < 0) {
+ p_index = patterns.size();
+ }
+ patterns.insert(p_index, p_pattern);
+ emit_changed();
+ return p_index;
+}
+
+Ref<TileMapPattern> TileSet::get_pattern(int p_index) {
+ ERR_FAIL_INDEX_V(p_index, (int)patterns.size(), Ref<TileMapPattern>());
+ return patterns[p_index];
+}
+
+void TileSet::remove_pattern(int p_index) {
+ ERR_FAIL_INDEX(p_index, (int)patterns.size());
+ patterns.remove(p_index);
+ emit_changed();
+}
+
+int TileSet::get_patterns_count() {
+ return patterns.size();
+}
+
Vector<Vector2> TileSet::get_tile_shape_polygon() {
Vector<Vector2> points;
if (tile_shape == TileSet::TILE_SHAPE_SQUARE) {
@@ -2483,6 +2697,12 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) {
return true;
}
return false;
+ } else if (components.size() == 1 && components[0].begins_with("pattern_") && components[0].trim_prefix("pattern_").is_valid_int()) {
+ int pattern_index = components[0].trim_prefix("pattern_").to_int();
+ for (int i = patterns.size(); i <= pattern_index; i++) {
+ add_pattern(p_value);
+ }
+ return true;
}
#ifndef DISABLE_DEPRECATED
@@ -2606,6 +2826,13 @@ bool TileSet::_get(const StringName &p_name, Variant &r_ret) const {
return true;
}
return false;
+ } else if (components.size() == 1 && components[0].begins_with("pattern_") && components[0].trim_prefix("pattern_").is_valid_int()) {
+ int pattern_index = components[0].trim_prefix("pattern_").to_int();
+ if (pattern_index < 0 || pattern_index >= (int)patterns.size()) {
+ return false;
+ }
+ r_ret = patterns[pattern_index];
+ return true;
}
return false;
@@ -2686,6 +2913,11 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/source_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/coords_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/alternative_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+
+ // Patterns.
+ for (unsigned int pattern_index = 0; pattern_index < patterns.size(); pattern_index++) {
+ p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("pattern_%d", pattern_index), PROPERTY_HINT_RESOURCE_TYPE, "TileMapPattern", PROPERTY_USAGE_NOEDITOR));
+ }
}
void TileSet::_validate_property(PropertyInfo &property) const {
@@ -2799,6 +3031,12 @@ void TileSet::_bind_methods() {
ClassDB::bind_method(D_METHOD("cleanup_invalid_tile_proxies"), &TileSet::cleanup_invalid_tile_proxies);
ClassDB::bind_method(D_METHOD("clear_tile_proxies"), &TileSet::clear_tile_proxies);
+ // Patterns
+ ClassDB::bind_method(D_METHOD("add_pattern", "pattern", "index"), &TileSet::add_pattern, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("get_pattern", "index"), &TileSet::get_pattern, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("remove_pattern", "index"), &TileSet::remove_pattern);
+ ClassDB::bind_method(D_METHOD("get_patterns_count"), &TileSet::get_patterns_count);
+
ADD_GROUP("Rendering", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uv_clipping"), "set_uv_clipping", "is_uv_clipping");
ADD_ARRAY("occlusion_layers", "occlusion_layer_");
diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h
index 351bdff89d..530c90920f 100644
--- a/scene/resources/tile_set.h
+++ b/scene/resources/tile_set.h
@@ -60,6 +60,84 @@ class TileSetPluginAtlasRendering;
class TileSetPluginAtlasPhysics;
class TileSetPluginAtlasNavigation;
+union TileMapCell {
+ struct {
+ int32_t source_id : 16;
+ int16_t coord_x : 16;
+ int16_t coord_y : 16;
+ int32_t alternative_tile : 16;
+ };
+
+ uint64_t _u64t;
+ TileMapCell(int p_source_id = -1, Vector2i p_atlas_coords = Vector2i(-1, -1), int p_alternative_tile = -1) { // default are INVALID_SOURCE, INVALID_ATLAS_COORDS, INVALID_TILE_ALTERNATIVE
+ source_id = p_source_id;
+ set_atlas_coords(p_atlas_coords);
+ alternative_tile = p_alternative_tile;
+ }
+
+ Vector2i get_atlas_coords() const {
+ return Vector2i(coord_x, coord_y);
+ }
+
+ void set_atlas_coords(const Vector2i &r_coords) {
+ coord_x = r_coords.x;
+ coord_y = r_coords.y;
+ }
+
+ bool operator<(const TileMapCell &p_other) const {
+ if (source_id == p_other.source_id) {
+ if (coord_x == p_other.coord_x) {
+ if (coord_y == p_other.coord_y) {
+ return alternative_tile < p_other.alternative_tile;
+ } else {
+ return coord_y < p_other.coord_y;
+ }
+ } else {
+ return coord_x < p_other.coord_x;
+ }
+ } else {
+ return source_id < p_other.source_id;
+ }
+ }
+
+ bool operator!=(const TileMapCell &p_other) const {
+ return !(source_id == p_other.source_id && coord_x == p_other.coord_x && coord_y == p_other.coord_y && alternative_tile == p_other.alternative_tile);
+ }
+};
+
+class TileMapPattern : public Resource {
+ GDCLASS(TileMapPattern, Resource);
+
+ Vector2i size;
+ Map<Vector2i, TileMapCell> pattern;
+
+ void _set_tile_data(const Vector<int> &p_data);
+ Vector<int> _get_tile_data() const;
+
+protected:
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
+
+ static void _bind_methods();
+
+public:
+ void set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile = 0);
+ bool has_cell(const Vector2i &p_coords) const;
+ void remove_cell(const Vector2i &p_coords, bool p_update_size = true);
+ int get_cell_source_id(const Vector2i &p_coords) const;
+ Vector2i get_cell_atlas_coords(const Vector2i &p_coords) const;
+ int get_cell_alternative_tile(const Vector2i &p_coords) const;
+
+ TypedArray<Vector2i> get_used_cells() const;
+
+ Vector2i get_size() const;
+ void set_size(const Vector2i &p_size);
+ bool is_empty() const;
+
+ void clear();
+};
+
class TileSet : public Resource {
GDCLASS(TileSet, Resource);
@@ -245,6 +323,8 @@ private:
int next_source_id = 0;
// ---------------------
+ LocalVector<Ref<TileMapPattern>> patterns;
+
void _compute_next_source_id();
void _source_changed();
@@ -384,6 +464,12 @@ public:
void cleanup_invalid_tile_proxies();
void clear_tile_proxies();
+ // Patterns.
+ int add_pattern(Ref<TileMapPattern> p_pattern, int p_index = -1);
+ Ref<TileMapPattern> get_pattern(int p_index);
+ void remove_pattern(int p_index);
+ int get_patterns_count();
+
// Helpers
Vector<Vector2> get_tile_shape_polygon();
void draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform, Color p_color, bool p_filled = false, Ref<Texture2D> p_texture = Ref<Texture2D>());