1/**************************************************************************/
2/* tile_set_editor.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "tile_set_editor.h"
32
33#include "tile_data_editors.h"
34#include "tiles_editor_plugin.h"
35
36#include "editor/editor_file_system.h"
37#include "editor/editor_inspector.h"
38#include "editor/editor_node.h"
39#include "editor/editor_scale.h"
40#include "editor/editor_settings.h"
41#include "editor/editor_undo_redo_manager.h"
42#include "editor/gui/editor_file_dialog.h"
43
44#include "scene/gui/box_container.h"
45#include "scene/gui/control.h"
46#include "scene/gui/dialogs.h"
47#include "scene/gui/tab_container.h"
48
49TileSetEditor *TileSetEditor::singleton = nullptr;
50
51void TileSetEditor::_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
52 ERR_FAIL_COND(!tile_set.is_valid());
53
54 if (!_can_drop_data_fw(p_point, p_data, p_from)) {
55 return;
56 }
57
58 if (p_from == sources_list) {
59 // Handle dropping a texture in the list of atlas resources.
60 Dictionary d = p_data;
61 Vector<String> files = d["files"];
62 _load_texture_files(files);
63 }
64}
65
66bool TileSetEditor::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
67 ERR_FAIL_COND_V(!tile_set.is_valid(), false);
68
69 if (read_only) {
70 return false;
71 }
72
73 if (p_from == sources_list) {
74 Dictionary d = p_data;
75
76 if (!d.has("type")) {
77 return false;
78 }
79
80 // Check if we have a Texture2D.
81 if (String(d["type"]) == "files") {
82 Vector<String> files = d["files"];
83
84 if (files.size() == 0) {
85 return false;
86 }
87
88 for (int i = 0; i < files.size(); i++) {
89 String file = files[i];
90 String ftype = EditorFileSystem::get_singleton()->get_file_type(file);
91
92 if (!ClassDB::is_parent_class(ftype, "Texture2D")) {
93 return false;
94 }
95 }
96
97 return true;
98 }
99 }
100 return false;
101}
102
103void TileSetEditor::_load_texture_files(const Vector<String> &p_paths) {
104 int source_id = TileSet::INVALID_SOURCE;
105 Vector<Ref<TileSetAtlasSource>> atlases;
106
107 for (const String &p_path : p_paths) {
108 Ref<Texture2D> texture = ResourceLoader::load(p_path);
109
110 if (texture.is_null()) {
111 EditorNode::get_singleton()->show_warning(TTR("Invalid texture selected."));
112 continue;
113 }
114
115 // Retrieve the id for the next created source.
116 source_id = tile_set->get_next_source_id();
117
118 // Actually create the new source.
119 Ref<TileSetAtlasSource> atlas_source = memnew(TileSetAtlasSource);
120 atlas_source->set_texture(texture);
121
122 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
123 undo_redo->create_action(TTR("Add a new atlas source"));
124 undo_redo->add_do_method(*tile_set, "add_source", atlas_source, source_id);
125 undo_redo->add_do_method(*atlas_source, "set_texture_region_size", tile_set->get_tile_size());
126 undo_redo->add_undo_method(*tile_set, "remove_source", source_id);
127 undo_redo->commit_action();
128
129 atlases.append(atlas_source);
130 }
131
132 if (!atlases.is_empty()) {
133 tile_set_atlas_source_editor->init_new_atlases(atlases);
134 }
135
136 // Update the selected source (thus triggering an update).
137 _update_sources_list(source_id);
138}
139
140void TileSetEditor::_update_sources_list(int force_selected_id) {
141 if (tile_set.is_null()) {
142 return;
143 }
144
145 // Get the previously selected id.
146 int old_selected = TileSet::INVALID_SOURCE;
147 if (sources_list->get_current() >= 0) {
148 int source_id = sources_list->get_item_metadata(sources_list->get_current());
149 if (tile_set->has_source(source_id)) {
150 old_selected = source_id;
151 }
152 }
153
154 int to_select = TileSet::INVALID_SOURCE;
155 if (force_selected_id >= 0) {
156 to_select = force_selected_id;
157 } else if (old_selected >= 0) {
158 to_select = old_selected;
159 }
160
161 // Clear the list.
162 sources_list->clear();
163
164 // Update the atlas sources.
165 List<int> source_ids = TilesEditorUtils::get_singleton()->get_sorted_sources(tile_set);
166 for (const int &source_id : source_ids) {
167 TileSetSource *source = *tile_set->get_source(source_id);
168
169 Ref<Texture2D> texture;
170 String item_text;
171
172 // Common to all type of sources.
173 if (!source->get_name().is_empty()) {
174 item_text = source->get_name();
175 }
176
177 // Atlas source.
178 TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
179 if (atlas_source) {
180 texture = atlas_source->get_texture();
181 if (item_text.is_empty()) {
182 if (texture.is_valid()) {
183 item_text = texture->get_path().get_file();
184 } else {
185 item_text = vformat(TTR("No Texture Atlas Source (ID: %d)"), source_id);
186 }
187 }
188 }
189
190 // Scene collection source.
191 TileSetScenesCollectionSource *scene_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source);
192 if (scene_collection_source) {
193 texture = get_editor_theme_icon(SNAME("PackedScene"));
194 if (item_text.is_empty()) {
195 if (scene_collection_source->get_scene_tiles_count() > 0) {
196 item_text = vformat(TTR("Scene Collection Source (ID: %d)"), source_id);
197 } else {
198 item_text = vformat(TTR("Empty Scene Collection Source (ID: %d)"), source_id);
199 }
200 }
201 }
202
203 // Use default if not valid.
204 if (item_text.is_empty()) {
205 item_text = vformat(TTR("Unknown Type Source (ID: %d)"), source_id);
206 }
207 if (!texture.is_valid()) {
208 texture = missing_texture_texture;
209 }
210
211 sources_list->add_item(item_text, texture);
212 sources_list->set_item_metadata(-1, source_id);
213 }
214
215 // Set again the current selected item if needed.
216 if (to_select >= 0) {
217 for (int i = 0; i < sources_list->get_item_count(); i++) {
218 if ((int)sources_list->get_item_metadata(i) == to_select) {
219 sources_list->set_current(i);
220 sources_list->ensure_current_is_visible();
221 if (old_selected != to_select) {
222 sources_list->emit_signal(SNAME("item_selected"), sources_list->get_current());
223 }
224 break;
225 }
226 }
227 }
228
229 // If nothing is selected, select the first entry.
230 if (sources_list->get_current() < 0 && sources_list->get_item_count() > 0) {
231 sources_list->set_current(0);
232 if (old_selected != int(sources_list->get_item_metadata(0))) {
233 sources_list->emit_signal(SNAME("item_selected"), sources_list->get_current());
234 }
235 }
236
237 // If there is no source left, hide all editors and show the label.
238 _source_selected(sources_list->get_current());
239
240 // Synchronize the lists.
241 TilesEditorUtils::get_singleton()->set_sources_lists_current(sources_list->get_current());
242}
243
244void TileSetEditor::_source_selected(int p_source_index) {
245 ERR_FAIL_COND(!tile_set.is_valid());
246
247 // Update the selected source.
248 sources_delete_button->set_disabled(p_source_index < 0 || read_only);
249
250 if (p_source_index >= 0) {
251 int source_id = sources_list->get_item_metadata(p_source_index);
252 TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(source_id));
253 TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(*tile_set->get_source(source_id));
254 if (atlas_source) {
255 no_source_selected_label->hide();
256 tile_set_atlas_source_editor->edit(*tile_set, atlas_source, source_id);
257 tile_set_atlas_source_editor->show();
258 tile_set_scenes_collection_source_editor->hide();
259 } else if (scenes_collection_source) {
260 no_source_selected_label->hide();
261 tile_set_atlas_source_editor->hide();
262 tile_set_scenes_collection_source_editor->edit(*tile_set, scenes_collection_source, source_id);
263 tile_set_scenes_collection_source_editor->show();
264 } else {
265 no_source_selected_label->show();
266 tile_set_atlas_source_editor->hide();
267 tile_set_scenes_collection_source_editor->hide();
268 }
269 } else {
270 no_source_selected_label->show();
271 tile_set_atlas_source_editor->hide();
272 tile_set_scenes_collection_source_editor->hide();
273 }
274}
275
276void TileSetEditor::_source_delete_pressed() {
277 ERR_FAIL_COND(!tile_set.is_valid());
278
279 // Update the selected source.
280 int to_delete = sources_list->get_item_metadata(sources_list->get_current());
281
282 Ref<TileSetSource> source = tile_set->get_source(to_delete);
283
284 // Remove the source.
285 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
286 undo_redo->create_action(TTR("Remove source"));
287 undo_redo->add_do_method(*tile_set, "remove_source", to_delete);
288 undo_redo->add_undo_method(*tile_set, "add_source", source, to_delete);
289 undo_redo->commit_action();
290
291 _update_sources_list();
292}
293
294void TileSetEditor::_source_add_id_pressed(int p_id_pressed) {
295 ERR_FAIL_COND(!tile_set.is_valid());
296
297 switch (p_id_pressed) {
298 case 0: {
299 if (!texture_file_dialog) {
300 texture_file_dialog = memnew(EditorFileDialog);
301 add_child(texture_file_dialog);
302 texture_file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES);
303 texture_file_dialog->connect("files_selected", callable_mp(this, &TileSetEditor::_load_texture_files));
304
305 List<String> extensions;
306 ResourceLoader::get_recognized_extensions_for_type("Texture2D", &extensions);
307 for (const String &E : extensions) {
308 texture_file_dialog->add_filter("*." + E, E.to_upper());
309 }
310 }
311 texture_file_dialog->popup_file_dialog();
312 } break;
313 case 1: {
314 int source_id = tile_set->get_next_source_id();
315
316 Ref<TileSetScenesCollectionSource> scene_collection_source = memnew(TileSetScenesCollectionSource);
317
318 // Add a new source.
319 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
320 undo_redo->create_action(TTR("Add atlas source"));
321 undo_redo->add_do_method(*tile_set, "add_source", scene_collection_source, source_id);
322 undo_redo->add_undo_method(*tile_set, "remove_source", source_id);
323 undo_redo->commit_action();
324
325 _update_sources_list(source_id);
326 } break;
327 default:
328 ERR_FAIL();
329 }
330}
331
332void TileSetEditor::_sources_advanced_menu_id_pressed(int p_id_pressed) {
333 ERR_FAIL_COND(!tile_set.is_valid());
334
335 switch (p_id_pressed) {
336 case 0: {
337 atlas_merging_dialog->update_tile_set(tile_set);
338 atlas_merging_dialog->popup_centered_ratio(0.5);
339 } break;
340 case 1: {
341 tile_proxies_manager_dialog->update_tile_set(tile_set);
342 tile_proxies_manager_dialog->popup_centered_ratio(0.5);
343 } break;
344 }
345}
346
347void TileSetEditor::_set_source_sort(int p_sort) {
348 TilesEditorUtils::get_singleton()->set_sorting_option(p_sort);
349 for (int i = 0; i != TilesEditorUtils::SOURCE_SORT_MAX; i++) {
350 source_sort_button->get_popup()->set_item_checked(i, (i == (int)p_sort));
351 }
352
353 int old_selected = TileSet::INVALID_SOURCE;
354 if (sources_list->get_current() >= 0) {
355 int source_id = sources_list->get_item_metadata(sources_list->get_current());
356 if (tile_set->has_source(source_id)) {
357 old_selected = source_id;
358 }
359 }
360 _update_sources_list(old_selected);
361 EditorSettings::get_singleton()->set_project_metadata("editor_metadata", "tile_source_sort", p_sort);
362}
363
364void TileSetEditor::_notification(int p_what) {
365 switch (p_what) {
366 case NOTIFICATION_THEME_CHANGED: {
367 sources_delete_button->set_icon(get_editor_theme_icon(SNAME("Remove")));
368 sources_add_button->set_icon(get_editor_theme_icon(SNAME("Add")));
369 source_sort_button->set_icon(get_editor_theme_icon(SNAME("Sort")));
370 sources_advanced_menu_button->set_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
371 missing_texture_texture = get_editor_theme_icon(SNAME("TileSet"));
372 expanded_area->add_theme_style_override("panel", get_theme_stylebox("panel", "Tree"));
373 _update_sources_list();
374 } break;
375
376 case NOTIFICATION_INTERNAL_PROCESS: {
377 if (tile_set_changed_needs_update) {
378 if (tile_set.is_valid()) {
379 tile_set->set_edited(true);
380 }
381
382 read_only = false;
383 if (tile_set.is_valid()) {
384 read_only = EditorNode::get_singleton()->is_resource_read_only(tile_set);
385 }
386
387 _update_sources_list();
388 _update_patterns_list();
389
390 sources_add_button->set_disabled(read_only);
391 sources_advanced_menu_button->set_disabled(read_only);
392 source_sort_button->set_disabled(read_only);
393
394 tile_set_changed_needs_update = false;
395 }
396 } break;
397
398 case NOTIFICATION_VISIBILITY_CHANGED: {
399 if (!is_visible_in_tree()) {
400 remove_expanded_editor();
401 }
402 } break;
403 }
404}
405
406void TileSetEditor::_patterns_item_list_gui_input(const Ref<InputEvent> &p_event) {
407 ERR_FAIL_COND(!tile_set.is_valid());
408
409 if (EditorNode::get_singleton()->is_resource_read_only(tile_set)) {
410 return;
411 }
412
413 if (ED_IS_SHORTCUT("tiles_editor/delete", p_event) && p_event->is_pressed() && !p_event->is_echo()) {
414 Vector<int> selected = patterns_item_list->get_selected_items();
415 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
416 undo_redo->create_action(TTR("Remove TileSet patterns"));
417 for (int i = 0; i < selected.size(); i++) {
418 int pattern_index = selected[i];
419 undo_redo->add_do_method(*tile_set, "remove_pattern", pattern_index);
420 undo_redo->add_undo_method(*tile_set, "add_pattern", tile_set->get_pattern(pattern_index), pattern_index);
421 }
422 undo_redo->commit_action();
423 patterns_item_list->accept_event();
424 }
425}
426
427void TileSetEditor::_pattern_preview_done(Ref<TileMapPattern> p_pattern, Ref<Texture2D> p_texture) {
428 // TODO optimize ?
429 for (int i = 0; i < patterns_item_list->get_item_count(); i++) {
430 if (patterns_item_list->get_item_metadata(i) == p_pattern) {
431 patterns_item_list->set_item_icon(i, p_texture);
432 break;
433 }
434 }
435}
436
437void TileSetEditor::_update_patterns_list() {
438 ERR_FAIL_COND(!tile_set.is_valid());
439
440 // Recreate the items.
441 patterns_item_list->clear();
442 for (int i = 0; i < tile_set->get_patterns_count(); i++) {
443 int id = patterns_item_list->add_item("");
444 patterns_item_list->set_item_metadata(id, tile_set->get_pattern(i));
445 patterns_item_list->set_item_tooltip(id, vformat(TTR("Index: %d"), i));
446 TilesEditorUtils::get_singleton()->queue_pattern_preview(tile_set, tile_set->get_pattern(i), callable_mp(this, &TileSetEditor::_pattern_preview_done));
447 }
448
449 // Update the label visibility.
450 patterns_help_label->set_visible(patterns_item_list->get_item_count() == 0);
451}
452
453void TileSetEditor::_tile_set_changed() {
454 tile_set_changed_needs_update = true;
455}
456
457void TileSetEditor::_tab_changed(int p_tab_changed) {
458 split_container->set_visible(p_tab_changed == 0);
459 patterns_item_list->set_visible(p_tab_changed == 1);
460}
461
462void 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) {
463 EditorUndoRedoManager *undo_redo_man = Object::cast_to<EditorUndoRedoManager>(p_undo_redo);
464 ERR_FAIL_NULL(undo_redo_man);
465
466 TileSet *ed_tile_set = Object::cast_to<TileSet>(p_edited);
467 if (!ed_tile_set) {
468 return;
469 }
470
471 Vector<String> components = String(p_array_prefix).split("/", true, 2);
472
473 // Compute the array indices to save.
474 int begin = 0;
475 int end;
476 if (p_array_prefix == "occlusion_layer_") {
477 end = ed_tile_set->get_occlusion_layers_count();
478 } else if (p_array_prefix == "physics_layer_") {
479 end = ed_tile_set->get_physics_layers_count();
480 } else if (p_array_prefix == "terrain_set_") {
481 end = ed_tile_set->get_terrain_sets_count();
482 } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") {
483 int terrain_set = components[0].trim_prefix("terrain_set_").to_int();
484 end = ed_tile_set->get_terrains_count(terrain_set);
485 } else if (p_array_prefix == "navigation_layer_") {
486 end = ed_tile_set->get_navigation_layers_count();
487 } else if (p_array_prefix == "custom_data_layer_") {
488 end = ed_tile_set->get_custom_data_layers_count();
489 } else {
490 ERR_FAIL_MSG("Invalid array prefix for TileSet.");
491 }
492 if (p_from_index < 0) {
493 // Adding new.
494 if (p_to_pos >= 0) {
495 begin = p_to_pos;
496 } else {
497 end = 0; // Nothing to save when adding at the end.
498 }
499 } else if (p_to_pos < 0) {
500 // Removing.
501 begin = p_from_index;
502 } else {
503 // Moving.
504 begin = MIN(p_from_index, p_to_pos);
505 end = MIN(MAX(p_from_index, p_to_pos) + 1, end);
506 }
507
508#define ADD_UNDO(obj, property) undo_redo_man->add_undo_property(obj, property, obj->get(property));
509
510 // Add undo method to adding array element.
511 if (p_array_prefix == "occlusion_layer_") {
512 if (p_from_index < 0) {
513 undo_redo_man->add_undo_method(ed_tile_set, "remove_occlusion_layer", p_to_pos < 0 ? ed_tile_set->get_occlusion_layers_count() : p_to_pos);
514 }
515 } else if (p_array_prefix == "physics_layer_") {
516 if (p_from_index < 0) {
517 undo_redo_man->add_undo_method(ed_tile_set, "remove_physics_layer", p_to_pos < 0 ? ed_tile_set->get_physics_layers_count() : p_to_pos);
518 }
519 } else if (p_array_prefix == "terrain_set_") {
520 if (p_from_index < 0) {
521 undo_redo_man->add_undo_method(ed_tile_set, "remove_terrain_set", p_to_pos < 0 ? ed_tile_set->get_terrain_sets_count() : p_to_pos);
522 }
523 } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") {
524 int terrain_set = components[0].trim_prefix("terrain_set_").to_int();
525 if (p_from_index < 0) {
526 undo_redo_man->add_undo_method(ed_tile_set, "remove_terrain", terrain_set, p_to_pos < 0 ? ed_tile_set->get_terrains_count(terrain_set) : p_to_pos);
527 }
528 } else if (p_array_prefix == "navigation_layer_") {
529 if (p_from_index < 0) {
530 undo_redo_man->add_undo_method(ed_tile_set, "remove_navigation_layer", p_to_pos < 0 ? ed_tile_set->get_navigation_layers_count() : p_to_pos);
531 }
532 } else if (p_array_prefix == "custom_data_layer_") {
533 if (p_from_index < 0) {
534 undo_redo_man->add_undo_method(ed_tile_set, "remove_custom_data_layer", p_to_pos < 0 ? ed_tile_set->get_custom_data_layers_count() : p_to_pos);
535 }
536 }
537
538 // Save layers' properties.
539 List<PropertyInfo> properties;
540 ed_tile_set->get_property_list(&properties);
541 for (PropertyInfo pi : properties) {
542 if (pi.name.begins_with(p_array_prefix)) {
543 String str = pi.name.trim_prefix(p_array_prefix);
544 int to_char_index = 0;
545 while (to_char_index < str.length()) {
546 if (!is_digit(str[to_char_index])) {
547 break;
548 }
549 to_char_index++;
550 }
551 if (to_char_index > 0) {
552 int array_index = str.left(to_char_index).to_int();
553 if (array_index >= begin && array_index < end) {
554 ADD_UNDO(ed_tile_set, pi.name);
555 }
556 }
557 }
558 }
559
560 // Save properties for TileSetAtlasSources tile data
561 for (int i = 0; i < ed_tile_set->get_source_count(); i++) {
562 int source_id = ed_tile_set->get_source_id(i);
563
564 Ref<TileSetAtlasSource> tas = ed_tile_set->get_source(source_id);
565 if (tas.is_valid()) {
566 for (int j = 0; j < tas->get_tiles_count(); j++) {
567 Vector2i tile_id = tas->get_tile_id(j);
568 for (int k = 0; k < tas->get_alternative_tiles_count(tile_id); k++) {
569 int alternative_id = tas->get_alternative_tile_id(tile_id, k);
570 TileData *tile_data = tas->get_tile_data(tile_id, alternative_id);
571 ERR_FAIL_NULL(tile_data);
572
573 // Actually saving stuff.
574 if (p_array_prefix == "occlusion_layer_") {
575 for (int layer_index = begin; layer_index < end; layer_index++) {
576 ADD_UNDO(tile_data, vformat("occlusion_layer_%d/polygon", layer_index));
577 }
578 } else if (p_array_prefix == "physics_layer_") {
579 for (int layer_index = begin; layer_index < end; layer_index++) {
580 ADD_UNDO(tile_data, vformat("physics_layer_%d/polygons_count", layer_index));
581 for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(layer_index); polygon_index++) {
582 ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", layer_index, polygon_index));
583 ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, polygon_index));
584 ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, polygon_index));
585 }
586 }
587 } else if (p_array_prefix == "terrain_set_") {
588 ADD_UNDO(tile_data, "terrain_set");
589 for (int terrain_set_index = begin; terrain_set_index < end; terrain_set_index++) {
590 for (int l = 0; l < TileSet::CELL_NEIGHBOR_MAX; l++) {
591 TileSet::CellNeighbor bit = TileSet::CellNeighbor(l);
592 if (tile_data->is_valid_terrain_peering_bit(bit)) {
593 ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[l]));
594 }
595 }
596 }
597 } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") {
598 for (int terrain_index = 0; terrain_index < TileSet::CELL_NEIGHBOR_MAX; terrain_index++) {
599 TileSet::CellNeighbor bit = TileSet::CellNeighbor(terrain_index);
600 if (tile_data->is_valid_terrain_peering_bit(bit)) {
601 ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[terrain_index]));
602 }
603 }
604 } else if (p_array_prefix == "navigation_layer_") {
605 for (int layer_index = begin; layer_index < end; layer_index++) {
606 ADD_UNDO(tile_data, vformat("navigation_layer_%d/polygon", layer_index));
607 }
608 } else if (p_array_prefix == "custom_data_layer_") {
609 for (int layer_index = begin; layer_index < end; layer_index++) {
610 ADD_UNDO(tile_data, vformat("custom_data_%d", layer_index));
611 }
612 }
613 }
614 }
615 }
616 }
617#undef ADD_UNDO
618
619 // Add do method to add/remove array element.
620 if (p_array_prefix == "occlusion_layer_") {
621 if (p_from_index < 0) {
622 undo_redo_man->add_do_method(ed_tile_set, "add_occlusion_layer", p_to_pos);
623 } else if (p_to_pos < 0) {
624 undo_redo_man->add_do_method(ed_tile_set, "remove_occlusion_layer", p_from_index);
625 } else {
626 undo_redo_man->add_do_method(ed_tile_set, "move_occlusion_layer", p_from_index, p_to_pos);
627 }
628 } else if (p_array_prefix == "physics_layer_") {
629 if (p_from_index < 0) {
630 undo_redo_man->add_do_method(ed_tile_set, "add_physics_layer", p_to_pos);
631 } else if (p_to_pos < 0) {
632 undo_redo_man->add_do_method(ed_tile_set, "remove_physics_layer", p_from_index);
633 } else {
634 undo_redo_man->add_do_method(ed_tile_set, "move_physics_layer", p_from_index, p_to_pos);
635 }
636 } else if (p_array_prefix == "terrain_set_") {
637 if (p_from_index < 0) {
638 undo_redo_man->add_do_method(ed_tile_set, "add_terrain_set", p_to_pos);
639 } else if (p_to_pos < 0) {
640 undo_redo_man->add_do_method(ed_tile_set, "remove_terrain_set", p_from_index);
641 } else {
642 undo_redo_man->add_do_method(ed_tile_set, "move_terrain_set", p_from_index, p_to_pos);
643 }
644 } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") {
645 int terrain_set = components[0].trim_prefix("terrain_set_").to_int();
646 if (p_from_index < 0) {
647 undo_redo_man->add_do_method(ed_tile_set, "add_terrain", terrain_set, p_to_pos);
648 } else if (p_to_pos < 0) {
649 undo_redo_man->add_do_method(ed_tile_set, "remove_terrain", terrain_set, p_from_index);
650 } else {
651 undo_redo_man->add_do_method(ed_tile_set, "move_terrain", terrain_set, p_from_index, p_to_pos);
652 }
653 } else if (p_array_prefix == "navigation_layer_") {
654 if (p_from_index < 0) {
655 undo_redo_man->add_do_method(ed_tile_set, "add_navigation_layer", p_to_pos);
656 } else if (p_to_pos < 0) {
657 undo_redo_man->add_do_method(ed_tile_set, "remove_navigation_layer", p_from_index);
658 } else {
659 undo_redo_man->add_do_method(ed_tile_set, "move_navigation_layer", p_from_index, p_to_pos);
660 }
661 } else if (p_array_prefix == "custom_data_layer_") {
662 if (p_from_index < 0) {
663 undo_redo_man->add_do_method(ed_tile_set, "add_custom_data_layer", p_to_pos);
664 } else if (p_to_pos < 0) {
665 undo_redo_man->add_do_method(ed_tile_set, "remove_custom_data_layer", p_from_index);
666 } else {
667 undo_redo_man->add_do_method(ed_tile_set, "move_custom_data_layer", p_from_index, p_to_pos);
668 }
669 }
670}
671
672void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) {
673 EditorUndoRedoManager *undo_redo_man = Object::cast_to<EditorUndoRedoManager>(p_undo_redo);
674 ERR_FAIL_NULL(undo_redo_man);
675
676#define ADD_UNDO(obj, property) undo_redo_man->add_undo_property(obj, property, obj->get(property));
677 TileSet *ed_tile_set = Object::cast_to<TileSet>(p_edited);
678 if (ed_tile_set) {
679 Vector<String> components = p_property.split("/", true, 3);
680 for (int i = 0; i < ed_tile_set->get_source_count(); i++) {
681 int source_id = ed_tile_set->get_source_id(i);
682
683 Ref<TileSetAtlasSource> tas = ed_tile_set->get_source(source_id);
684 if (tas.is_valid()) {
685 for (int j = 0; j < tas->get_tiles_count(); j++) {
686 Vector2i tile_id = tas->get_tile_id(j);
687 for (int k = 0; k < tas->get_alternative_tiles_count(tile_id); k++) {
688 int alternative_id = tas->get_alternative_tile_id(tile_id, k);
689 TileData *tile_data = tas->get_tile_data(tile_id, alternative_id);
690 ERR_FAIL_NULL(tile_data);
691
692 if (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "mode") {
693 ADD_UNDO(tile_data, "terrain_set");
694 ADD_UNDO(tile_data, "terrain");
695 for (int l = 0; l < TileSet::CELL_NEIGHBOR_MAX; l++) {
696 TileSet::CellNeighbor bit = TileSet::CellNeighbor(l);
697 if (tile_data->is_valid_terrain_peering_bit(bit)) {
698 ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[l]));
699 }
700 }
701 } else if (components.size() == 2 && components[0].begins_with("custom_data_layer_") && components[0].trim_prefix("custom_data_layer_").is_valid_int() && components[1] == "type") {
702 int custom_data_layer = components[0].trim_prefix("custom_data_layer_").is_valid_int();
703 ADD_UNDO(tile_data, vformat("custom_data_%d", custom_data_layer));
704 }
705 }
706 }
707 }
708 }
709 }
710#undef ADD_UNDO
711}
712
713void TileSetEditor::edit(Ref<TileSet> p_tile_set) {
714 bool new_read_only_state = false;
715 if (p_tile_set.is_valid()) {
716 new_read_only_state = EditorNode::get_singleton()->is_resource_read_only(p_tile_set);
717 }
718
719 if (p_tile_set == tile_set && new_read_only_state == read_only) {
720 return;
721 }
722
723 // Remove listener.
724 if (tile_set.is_valid()) {
725 tile_set->disconnect_changed(callable_mp(this, &TileSetEditor::_tile_set_changed));
726 }
727
728 // Change the edited object.
729 tile_set = p_tile_set;
730
731 // Read-only status is false by default
732 read_only = new_read_only_state;
733
734 // Add the listener again and check for read-only status.
735 if (tile_set.is_valid()) {
736 sources_add_button->set_disabled(read_only);
737 sources_advanced_menu_button->set_disabled(read_only);
738 source_sort_button->set_disabled(read_only);
739
740 tile_set->connect_changed(callable_mp(this, &TileSetEditor::_tile_set_changed));
741 if (first_edit) {
742 first_edit = false;
743 _set_source_sort(EditorSettings::get_singleton()->get_project_metadata("editor_metadata", "tile_source_sort", 0));
744 } else {
745 _update_sources_list();
746 }
747 _update_patterns_list();
748 }
749}
750
751void TileSetEditor::add_expanded_editor(Control *p_editor) {
752 expanded_editor = p_editor;
753 expanded_editor_parent = p_editor->get_parent()->get_instance_id();
754
755 // Find the scrollable control this node belongs to.
756 Node *check_parent = expanded_editor->get_parent();
757 Control *parent_container = nullptr;
758 while (check_parent) {
759 parent_container = Object::cast_to<EditorInspector>(check_parent);
760 if (parent_container) {
761 break;
762 }
763 parent_container = Object::cast_to<ScrollContainer>(check_parent);
764 if (parent_container) {
765 break;
766 }
767 check_parent = check_parent->get_parent();
768 }
769 ERR_FAIL_NULL(parent_container);
770
771 expanded_editor->set_meta("reparented", true);
772 expanded_editor->reparent(expanded_area);
773 expanded_area->show();
774 expanded_area->set_size(Vector2(parent_container->get_global_rect().get_end().x - expanded_area->get_global_position().x, expanded_area->get_size().y));
775
776 for (SplitContainer *split : disable_on_expand) {
777 split->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN);
778 }
779}
780
781void TileSetEditor::remove_expanded_editor() {
782 if (!expanded_editor) {
783 return;
784 }
785
786 Node *original_parent = Object::cast_to<Node>(ObjectDB::get_instance(expanded_editor_parent));
787 if (original_parent) {
788 expanded_editor->remove_meta("reparented");
789 expanded_editor->reparent(original_parent);
790 } else {
791 expanded_editor->queue_free();
792 }
793 expanded_editor = nullptr;
794 expanded_editor_parent = ObjectID();
795 expanded_area->hide();
796
797 for (SplitContainer *split : disable_on_expand) {
798 split->set_dragger_visibility(SplitContainer::DRAGGER_VISIBLE);
799 }
800}
801
802void TileSetEditor::register_split(SplitContainer *p_split) {
803 disable_on_expand.push_back(p_split);
804}
805
806TileSetEditor::TileSetEditor() {
807 singleton = this;
808
809 set_process_internal(true);
810
811 VBoxContainer *main_vb = memnew(VBoxContainer);
812 add_child(main_vb);
813 main_vb->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
814
815 // TabBar.
816 tabs_bar = memnew(TabBar);
817 tabs_bar->set_tab_alignment(TabBar::ALIGNMENT_CENTER);
818 tabs_bar->set_clip_tabs(false);
819 tabs_bar->add_tab(TTR("Tiles"));
820 tabs_bar->add_tab(TTR("Patterns"));
821 tabs_bar->connect("tab_changed", callable_mp(this, &TileSetEditor::_tab_changed));
822
823 tile_set_toolbar = memnew(HBoxContainer);
824 tile_set_toolbar->set_h_size_flags(SIZE_EXPAND_FILL);
825 tile_set_toolbar->add_child(tabs_bar);
826 main_vb->add_child(tile_set_toolbar);
827
828 //// Tiles ////
829 // Split container.
830 split_container = memnew(HSplitContainer);
831 split_container->set_name(TTR("Tiles"));
832 split_container->set_h_size_flags(SIZE_EXPAND_FILL);
833 split_container->set_v_size_flags(SIZE_EXPAND_FILL);
834 main_vb->add_child(split_container);
835
836 // Sources list.
837 VBoxContainer *split_container_left_side = memnew(VBoxContainer);
838 split_container_left_side->set_h_size_flags(SIZE_EXPAND_FILL);
839 split_container_left_side->set_v_size_flags(SIZE_EXPAND_FILL);
840 split_container_left_side->set_stretch_ratio(0.25);
841 split_container_left_side->set_custom_minimum_size(Size2(70, 0) * EDSCALE);
842 split_container->add_child(split_container_left_side);
843
844 source_sort_button = memnew(MenuButton);
845 source_sort_button->set_flat(true);
846 source_sort_button->set_tooltip_text(TTR("Sort Sources"));
847
848 PopupMenu *p = source_sort_button->get_popup();
849 p->connect("id_pressed", callable_mp(this, &TileSetEditor::_set_source_sort));
850 p->add_radio_check_item(TTR("Sort by ID (Ascending)"), TilesEditorUtils::SOURCE_SORT_ID);
851 p->add_radio_check_item(TTR("Sort by ID (Descending)"), TilesEditorUtils::SOURCE_SORT_ID_REVERSE);
852 p->add_radio_check_item(TTR("Sort by Name (Ascending)"), TilesEditorUtils::SOURCE_SORT_NAME);
853 p->add_radio_check_item(TTR("Sort by Name (Descending)"), TilesEditorUtils::SOURCE_SORT_NAME_REVERSE);
854 p->set_item_checked(TilesEditorUtils::SOURCE_SORT_ID, true);
855
856 sources_list = memnew(ItemList);
857 sources_list->set_fixed_icon_size(Size2(60, 60) * EDSCALE);
858 sources_list->set_h_size_flags(SIZE_EXPAND_FILL);
859 sources_list->set_v_size_flags(SIZE_EXPAND_FILL);
860 sources_list->connect("item_selected", callable_mp(this, &TileSetEditor::_source_selected));
861 sources_list->connect("item_selected", callable_mp(TilesEditorUtils::get_singleton(), &TilesEditorUtils::set_sources_lists_current));
862 sources_list->connect("visibility_changed", callable_mp(TilesEditorUtils::get_singleton(), &TilesEditorUtils::synchronize_sources_list).bind(sources_list, source_sort_button));
863 sources_list->add_user_signal(MethodInfo("sort_request"));
864 sources_list->connect("sort_request", callable_mp(this, &TileSetEditor::_update_sources_list).bind(-1));
865 sources_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
866 SET_DRAG_FORWARDING_CDU(sources_list, TileSetEditor);
867 split_container_left_side->add_child(sources_list);
868
869 HBoxContainer *sources_bottom_actions = memnew(HBoxContainer);
870 sources_bottom_actions->set_alignment(BoxContainer::ALIGNMENT_END);
871 split_container_left_side->add_child(sources_bottom_actions);
872
873 sources_delete_button = memnew(Button);
874 sources_delete_button->set_flat(true);
875 sources_delete_button->set_disabled(true);
876 sources_delete_button->connect("pressed", callable_mp(this, &TileSetEditor::_source_delete_pressed));
877 sources_bottom_actions->add_child(sources_delete_button);
878
879 sources_add_button = memnew(MenuButton);
880 sources_add_button->set_flat(true);
881 sources_add_button->get_popup()->add_item(TTR("Atlas"));
882 sources_add_button->get_popup()->add_item(TTR("Scenes Collection"));
883 sources_add_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetEditor::_source_add_id_pressed));
884 sources_bottom_actions->add_child(sources_add_button);
885
886 sources_advanced_menu_button = memnew(MenuButton);
887 sources_advanced_menu_button->set_flat(true);
888 sources_advanced_menu_button->get_popup()->add_item(TTR("Open Atlas Merging Tool"));
889 sources_advanced_menu_button->get_popup()->add_item(TTR("Manage Tile Proxies"));
890 sources_advanced_menu_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetEditor::_sources_advanced_menu_id_pressed));
891 sources_bottom_actions->add_child(sources_advanced_menu_button);
892 sources_bottom_actions->add_child(source_sort_button);
893
894 atlas_merging_dialog = memnew(AtlasMergingDialog);
895 add_child(atlas_merging_dialog);
896
897 tile_proxies_manager_dialog = memnew(TileProxiesManagerDialog);
898 add_child(tile_proxies_manager_dialog);
899
900 // Right side container.
901 VBoxContainer *split_container_right_side = memnew(VBoxContainer);
902 split_container_right_side->set_h_size_flags(SIZE_EXPAND_FILL);
903 split_container_right_side->set_v_size_flags(SIZE_EXPAND_FILL);
904 split_container->add_child(split_container_right_side);
905
906 // No source selected.
907 no_source_selected_label = memnew(Label);
908 no_source_selected_label->set_text(TTR("No TileSet source selected. Select or create a TileSet source.\nYou can create a new source by using the Add button on the left or by dropping a tileset texture onto the source list."));
909 no_source_selected_label->set_h_size_flags(SIZE_EXPAND_FILL);
910 no_source_selected_label->set_v_size_flags(SIZE_EXPAND_FILL);
911 no_source_selected_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
912 no_source_selected_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
913 split_container_right_side->add_child(no_source_selected_label);
914
915 // Atlases editor.
916 tile_set_atlas_source_editor = memnew(TileSetAtlasSourceEditor);
917 tile_set_atlas_source_editor->set_h_size_flags(SIZE_EXPAND_FILL);
918 tile_set_atlas_source_editor->set_v_size_flags(SIZE_EXPAND_FILL);
919 tile_set_atlas_source_editor->connect("source_id_changed", callable_mp(this, &TileSetEditor::_update_sources_list));
920 split_container_right_side->add_child(tile_set_atlas_source_editor);
921 tile_set_atlas_source_editor->hide();
922
923 // Scenes collection editor.
924 tile_set_scenes_collection_source_editor = memnew(TileSetScenesCollectionSourceEditor);
925 tile_set_scenes_collection_source_editor->set_h_size_flags(SIZE_EXPAND_FILL);
926 tile_set_scenes_collection_source_editor->set_v_size_flags(SIZE_EXPAND_FILL);
927 tile_set_scenes_collection_source_editor->connect("source_id_changed", callable_mp(this, &TileSetEditor::_update_sources_list));
928 split_container_right_side->add_child(tile_set_scenes_collection_source_editor);
929 tile_set_scenes_collection_source_editor->hide();
930
931 //// Patterns ////
932 int thumbnail_size = 64;
933 patterns_item_list = memnew(ItemList);
934 patterns_item_list->set_max_columns(0);
935 patterns_item_list->set_icon_mode(ItemList::ICON_MODE_TOP);
936 patterns_item_list->set_fixed_column_width(thumbnail_size * 3 / 2);
937 patterns_item_list->set_max_text_lines(2);
938 patterns_item_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size));
939 patterns_item_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
940 patterns_item_list->connect("gui_input", callable_mp(this, &TileSetEditor::_patterns_item_list_gui_input));
941 main_vb->add_child(patterns_item_list);
942 patterns_item_list->hide();
943
944 patterns_help_label = memnew(Label);
945 patterns_help_label->set_text(TTR("Add new patterns in the TileMap editing mode."));
946 patterns_help_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
947 patterns_help_label->set_anchors_and_offsets_preset(Control::PRESET_CENTER);
948 patterns_item_list->add_child(patterns_help_label);
949
950 // Expanded editor
951 expanded_area = memnew(PanelContainer);
952 add_child(expanded_area);
953 expanded_area->set_anchors_and_offsets_preset(PRESET_LEFT_WIDE);
954 expanded_area->hide();
955
956 // Registers UndoRedo inspector callback.
957 EditorNode::get_editor_data().add_move_array_element_function(SNAME("TileSet"), callable_mp(this, &TileSetEditor::_move_tile_set_array_element));
958 EditorNode::get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetEditor::_undo_redo_inspector_callback));
959}
960
961void TileSourceInspectorPlugin::_show_id_edit_dialog(Object *p_for_source) {
962 if (!id_edit_dialog) {
963 id_edit_dialog = memnew(ConfirmationDialog);
964 TileSetEditor::get_singleton()->add_child(id_edit_dialog);
965
966 VBoxContainer *vbox = memnew(VBoxContainer);
967 id_edit_dialog->add_child(vbox);
968
969 Label *label = memnew(Label(TTR("Warning: Modifying a source ID will result in all TileMaps using that source to reference an invalid source instead. This may result in unexpected data loss. Change this ID carefully.")));
970 label->set_autowrap_mode(TextServer::AUTOWRAP_WORD);
971 vbox->add_child(label);
972
973 id_input = memnew(SpinBox);
974 vbox->add_child(id_input);
975 id_input->set_max(INT_MAX);
976
977 id_edit_dialog->connect("confirmed", callable_mp(this, &TileSourceInspectorPlugin::_confirm_change_id));
978 }
979 edited_source = p_for_source;
980 id_input->set_value(p_for_source->get("id"));
981 id_edit_dialog->popup_centered(Vector2i(400, 0) * EDSCALE);
982 callable_mp((Control *)id_input->get_line_edit(), &Control::grab_focus).call_deferred();
983}
984
985void TileSourceInspectorPlugin::_confirm_change_id() {
986 edited_source->set("id", id_input->get_value());
987 id_label->set_text(vformat(TTR("ID: %d"), edited_source->get("id"))); // Use get(), because the provided ID might've been invalid.
988}
989
990bool TileSourceInspectorPlugin::can_handle(Object *p_object) {
991 return p_object->is_class("TileSetAtlasSourceProxyObject") || p_object->is_class("TileSetScenesCollectionProxyObject");
992}
993
994bool TileSourceInspectorPlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {
995 if (p_path == "id") {
996 const Variant value = p_object->get("id");
997 if (value.get_type() == Variant::NIL) { // May happen if the object is not yet initialized.
998 return true;
999 }
1000
1001 HBoxContainer *hbox = memnew(HBoxContainer);
1002 hbox->set_alignment(BoxContainer::ALIGNMENT_CENTER);
1003
1004 id_label = memnew(Label(vformat(TTR("ID: %d"), value)));
1005 hbox->add_child(id_label);
1006
1007 Button *button = memnew(Button(TTR("Edit")));
1008 hbox->add_child(button);
1009 button->connect("pressed", callable_mp(this, &TileSourceInspectorPlugin::_show_id_edit_dialog).bind(p_object));
1010
1011 add_custom_control(hbox);
1012 return true;
1013 }
1014 return false;
1015}
1016