diff options
author | Gilles Roudière <gilles.roudiere@gmail.com> | 2021-09-29 17:48:27 +0200 |
---|---|---|
committer | Gilles Roudière <gilles.roudiere@gmail.com> | 2021-10-19 11:57:37 +0200 |
commit | 1a95f893c4ed4d4ac872a294c64d06ee70fa10bd (patch) | |
tree | fb0deddf534c76189f0044bca36b46ef2f8dc444 | |
parent | 4387f9645b1f54755506804770ba15c6c9cd5094 (diff) |
Implement TileMap patterns palette
-rw-r--r-- | doc/classes/Control.xml | 6 | ||||
-rw-r--r-- | doc/classes/TileMap.xml | 26 | ||||
-rw-r--r-- | doc/classes/TileMapPattern.xml | 85 | ||||
-rw-r--r-- | doc/classes/TileSet.xml | 28 | ||||
-rw-r--r-- | editor/plugins/editor_preview_plugins.h | 18 | ||||
-rw-r--r-- | editor/plugins/tiles/tile_map_editor.cpp | 577 | ||||
-rw-r--r-- | editor/plugins/tiles/tile_map_editor.h | 59 | ||||
-rw-r--r-- | editor/plugins/tiles/tile_set_editor.cpp | 88 | ||||
-rw-r--r-- | editor/plugins/tiles/tile_set_editor.h | 17 | ||||
-rw-r--r-- | editor/plugins/tiles/tiles_editor_plugin.cpp | 125 | ||||
-rw-r--r-- | editor/plugins/tiles/tiles_editor_plugin.h | 21 | ||||
-rw-r--r-- | scene/2d/tile_map.cpp | 107 | ||||
-rw-r--r-- | scene/2d/tile_map.h | 77 | ||||
-rw-r--r-- | scene/gui/control.cpp | 2 | ||||
-rw-r--r-- | scene/gui/control.h | 2 | ||||
-rw-r--r-- | scene/register_scene_types.cpp | 1 | ||||
-rw-r--r-- | scene/resources/tile_set.cpp | 238 | ||||
-rw-r--r-- | scene/resources/tile_set.h | 86 |
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>()); |