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 | |
49 | TileSetEditor *TileSetEditor::singleton = nullptr; |
50 | |
51 | void 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 | |
66 | bool 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 | |
103 | void 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 | |
140 | void 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 | |
244 | void 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 | |
276 | void 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 | |
294 | void 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 | |
332 | void TileSetEditor::(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 | |
347 | void 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 | |
364 | void 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 | |
406 | void 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 | |
427 | void 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 | |
437 | void 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 | |
453 | void TileSetEditor::_tile_set_changed() { |
454 | tile_set_changed_needs_update = true; |
455 | } |
456 | |
457 | void 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 | |
462 | 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) { |
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 | |
672 | void 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 | |
713 | void 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 | |
751 | void 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 | |
781 | void 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 | |
802 | void TileSetEditor::register_split(SplitContainer *p_split) { |
803 | disable_on_expand.push_back(p_split); |
804 | } |
805 | |
806 | TileSetEditor::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 | |
961 | void 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 | |
985 | void 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 | |
990 | bool TileSourceInspectorPlugin::can_handle(Object *p_object) { |
991 | return p_object->is_class("TileSetAtlasSourceProxyObject" ) || p_object->is_class("TileSetScenesCollectionProxyObject" ); |
992 | } |
993 | |
994 | bool 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 | |