1/**************************************************************************/
2/* tile_set_atlas_source_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_atlas_source_editor.h"
32
33#include "tiles_editor_plugin.h"
34
35#include "editor/editor_inspector.h"
36#include "editor/editor_node.h"
37#include "editor/editor_scale.h"
38#include "editor/editor_settings.h"
39#include "editor/editor_string_names.h"
40#include "editor/editor_undo_redo_manager.h"
41#include "editor/gui/editor_toaster.h"
42#include "editor/plugins/tiles/tile_set_editor.h"
43#include "editor/progress_dialog.h"
44
45#include "scene/gui/box_container.h"
46#include "scene/gui/button.h"
47#include "scene/gui/control.h"
48#include "scene/gui/item_list.h"
49#include "scene/gui/separator.h"
50#include "scene/gui/split_container.h"
51#include "scene/gui/tab_container.h"
52
53#include "core/core_string_names.h"
54#include "core/math/geometry_2d.h"
55#include "core/os/keyboard.h"
56
57void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::set_id(int p_id) {
58 ERR_FAIL_COND(p_id < 0);
59 if (source_id == p_id) {
60 return;
61 }
62 ERR_FAIL_COND_MSG(tile_set->has_source(p_id), vformat("Cannot change TileSet Atlas Source ID. Another source exists with id %d.", p_id));
63
64 int previous_source = source_id;
65 source_id = p_id; // source_id must be updated before, because it's used by the source list update.
66 tile_set->set_source_id(previous_source, p_id);
67 emit_signal(SNAME("changed"), "id");
68}
69
70int TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::get_id() const {
71 return source_id;
72}
73
74bool TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_set(const StringName &p_name, const Variant &p_value) {
75 if (p_name == "id") {
76 set_id(p_value);
77 return true;
78 }
79 String name = p_name;
80 if (name == "name") {
81 // Use the resource_name property to store the source's name.
82 name = "resource_name";
83 }
84 bool valid = false;
85 tile_set_atlas_source->set(name, p_value, &valid);
86 if (valid) {
87 emit_signal(SNAME("changed"), String(name).utf8().get_data());
88 }
89 return valid;
90}
91
92bool TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_get(const StringName &p_name, Variant &r_ret) const {
93 if (!tile_set_atlas_source.is_valid()) {
94 return false;
95 }
96 if (p_name == "id") {
97 r_ret = get_id();
98 return true;
99 }
100 String name = p_name;
101 if (name == "name") {
102 // Use the resource_name property to store the source's name.
103 name = "resource_name";
104 }
105 bool valid = false;
106 r_ret = tile_set_atlas_source->get(name, &valid);
107 return valid;
108}
109
110void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_get_property_list(List<PropertyInfo> *p_list) const {
111 p_list->push_back(PropertyInfo(Variant::NIL, TTR("Atlas"), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY));
112 p_list->push_back(PropertyInfo(Variant::INT, PNAME("id"), PROPERTY_HINT_RANGE, "0," + itos(INT_MAX) + ",1"));
113 p_list->push_back(PropertyInfo(Variant::STRING, PNAME("name")));
114 p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("texture"), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"));
115 p_list->push_back(PropertyInfo(Variant::VECTOR2I, PNAME("margins"), PROPERTY_HINT_NONE, "suffix:px"));
116 p_list->push_back(PropertyInfo(Variant::VECTOR2I, PNAME("separation"), PROPERTY_HINT_NONE, "suffix:px"));
117 p_list->push_back(PropertyInfo(Variant::VECTOR2I, PNAME("texture_region_size"), PROPERTY_HINT_NONE, "suffix:px"));
118 p_list->push_back(PropertyInfo(Variant::BOOL, PNAME("use_texture_padding")));
119}
120
121void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_bind_methods() {
122 ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what")));
123}
124
125void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::edit(Ref<TileSet> p_tile_set, Ref<TileSetAtlasSource> p_tile_set_atlas_source, int p_source_id) {
126 ERR_FAIL_COND(!p_tile_set_atlas_source.is_valid());
127 ERR_FAIL_COND(p_source_id < 0);
128 ERR_FAIL_COND(p_tile_set.is_valid() && p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source);
129
130 if (p_tile_set == tile_set && p_tile_set_atlas_source == tile_set_atlas_source && p_source_id == source_id) {
131 return;
132 }
133
134 // Disconnect to changes.
135 if (tile_set_atlas_source.is_valid()) {
136 tile_set_atlas_source->disconnect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed));
137 }
138
139 tile_set = p_tile_set;
140 tile_set_atlas_source = p_tile_set_atlas_source;
141 source_id = p_source_id;
142
143 // Connect to changes.
144 if (tile_set_atlas_source.is_valid()) {
145 if (!tile_set_atlas_source->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) {
146 tile_set_atlas_source->connect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed));
147 }
148 }
149
150 notify_property_list_changed();
151}
152
153// -- Proxy object used by the tile inspector --
154bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_name, const Variant &p_value) {
155 if (!tile_set_atlas_source.is_valid()) {
156 return false;
157 }
158
159 // ID and size related properties.
160 if (tiles.size() == 1) {
161 const Vector2i coords = tiles.front()->get().tile;
162 const int &alternative = tiles.front()->get().alternative;
163
164 if (alternative == 0) {
165 Vector<String> components = String(p_name).split("/", true, 2);
166 if (p_name == "atlas_coords") {
167 Vector2i as_vector2i = Vector2i(p_value);
168 bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(as_vector2i, tile_set_atlas_source->get_tile_size_in_atlas(coords), tile_set_atlas_source->get_tile_animation_columns(coords), tile_set_atlas_source->get_tile_animation_separation(coords), tile_set_atlas_source->get_tile_animation_frames_count(coords), coords);
169 ERR_FAIL_COND_V_EDMSG(!has_room_for_tile, false, "Cannot move the tile, invalid coordinates or not enough room in the atlas for the tile and its animation frames.");
170
171 if (tiles_set_atlas_source_editor->selection.front()->get().tile == coords) {
172 tiles_set_atlas_source_editor->selection.clear();
173 tiles_set_atlas_source_editor->selection.insert({ as_vector2i, 0 });
174 tiles_set_atlas_source_editor->_update_tile_id_label();
175 }
176
177 tile_set_atlas_source->move_tile_in_atlas(coords, as_vector2i);
178 tiles.clear();
179 tiles.insert({ as_vector2i, 0 });
180 emit_signal(SNAME("changed"), "atlas_coords");
181 return true;
182 } else if (p_name == "size_in_atlas") {
183 Vector2i as_vector2i = Vector2i(p_value);
184 bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(coords, as_vector2i, tile_set_atlas_source->get_tile_animation_columns(coords), tile_set_atlas_source->get_tile_animation_separation(coords), tile_set_atlas_source->get_tile_animation_frames_count(coords), coords);
185 ERR_FAIL_COND_V_EDMSG(!has_room_for_tile, false, "Invalid size or not enough room in the atlas for the tile.");
186 tile_set_atlas_source->move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i);
187 emit_signal(SNAME("changed"), "size_in_atlas");
188 return true;
189 }
190 } else if (alternative > 0) {
191 if (p_name == "alternative_id") {
192 int as_int = int(p_value);
193 ERR_FAIL_COND_V(as_int < 0, false);
194 ERR_FAIL_COND_V_MSG(tile_set_atlas_source->has_alternative_tile(coords, as_int), false, vformat("Cannot change alternative tile ID. Another alternative exists with id %d for tile at coords %s.", as_int, coords));
195
196 if (tiles_set_atlas_source_editor->selection.front()->get().alternative == alternative) {
197 tiles_set_atlas_source_editor->selection.clear();
198 tiles_set_atlas_source_editor->selection.insert({ coords, as_int });
199 }
200
201 int previous_alternative_tile = alternative;
202 tiles.clear();
203 tiles.insert({ coords, as_int }); // tiles must be updated before.
204 tile_set_atlas_source->set_alternative_tile_id(coords, previous_alternative_tile, as_int);
205
206 emit_signal(SNAME("changed"), "alternative_id");
207 return true;
208 }
209 }
210 }
211
212 // Animation.
213 // Check if all tiles have an alternative_id of 0.
214 bool all_alternatve_id_zero = true;
215 for (TileSelection tile : tiles) {
216 if (tile.alternative != 0) {
217 all_alternatve_id_zero = false;
218 break;
219 }
220 }
221
222 if (all_alternatve_id_zero) {
223 Vector<String> components = String(p_name).split("/", true, 2);
224 if (p_name == "animation_columns") {
225 for (TileSelection tile : tiles) {
226 bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), p_value, tile_set_atlas_source->get_tile_animation_separation(tile.tile), tile_set_atlas_source->get_tile_animation_frames_count(tile.tile), tile.tile);
227 if (!has_room_for_tile) {
228 ERR_PRINT(vformat("Cannot change the number of columns to %s for tile animation. Not enough room in the atlas to layout %s frame(s).", p_value, tile_set_atlas_source->get_tile_animation_frames_count(tile.tile)));
229 } else {
230 tile_set_atlas_source->set_tile_animation_columns(tile.tile, p_value);
231 }
232 }
233 emit_signal(SNAME("changed"), "animation_columns");
234 return true;
235 } else if (p_name == "animation_separation") {
236 for (TileSelection tile : tiles) {
237 bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), tile_set_atlas_source->get_tile_animation_columns(tile.tile), p_value, tile_set_atlas_source->get_tile_animation_frames_count(tile.tile), tile.tile);
238 if (!has_room_for_tile) {
239 ERR_PRINT(vformat("Cannot change separation between frames of the animation to %s. Not enough room in the atlas to layout %s frame(s).", p_value, tile_set_atlas_source->get_tile_animation_frames_count(tile.tile)));
240 } else {
241 tile_set_atlas_source->set_tile_animation_separation(tile.tile, p_value);
242 }
243 }
244 emit_signal(SNAME("changed"), "animation_separation");
245 return true;
246 } else if (p_name == "animation_speed") {
247 for (TileSelection tile : tiles) {
248 tile_set_atlas_source->set_tile_animation_speed(tile.tile, p_value);
249 }
250 emit_signal(SNAME("changed"), "animation_speed");
251 return true;
252 } else if (p_name == "animation_mode") {
253 for (TileSelection tile : tiles) {
254 tile_set_atlas_source->set_tile_animation_mode(tile.tile, VariantCaster<TileSetAtlasSource::TileAnimationMode>::cast(p_value));
255 }
256 emit_signal(SNAME("changed"), "animation_mode");
257 return true;
258 } else if (p_name == "animation_frames_count") {
259 for (TileSelection tile : tiles) {
260 int frame_count = p_value;
261 if (frame_count == 0) {
262 frame_count = 1;
263 }
264
265 bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), tile_set_atlas_source->get_tile_animation_columns(tile.tile), tile_set_atlas_source->get_tile_animation_separation(tile.tile), frame_count, tile.tile);
266 if (!has_room_for_tile) {
267 ERR_PRINT(vformat("Cannot add frames to the animation, not enough room in the atlas to layout %s frames.", frame_count));
268 } else {
269 tile_set_atlas_source->set_tile_animation_frames_count(tile.tile, frame_count);
270 }
271 }
272 notify_property_list_changed();
273 emit_signal(SNAME("changed"), "animation_separation");
274 return true;
275 } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) {
276 for (TileSelection tile : tiles) {
277 int frame = components[0].trim_prefix("animation_frame_").to_int();
278 if (frame < 0 || frame >= tile_set_atlas_source->get_tile_animation_frames_count(tile.tile)) {
279 ERR_PRINT(vformat("No tile animation frame with index %d", frame));
280 } else {
281 if (components[1] == "duration") {
282 tile_set_atlas_source->set_tile_animation_frame_duration(tile.tile, frame, p_value);
283 }
284 }
285 }
286 return true;
287 }
288 }
289
290 // Other properties.
291 bool any_valid = false;
292 for (const TileSelection &E : tiles) {
293 const Vector2i &coords = E.tile;
294 const int &alternative = E.alternative;
295
296 bool valid = false;
297 TileData *tile_data = tile_set_atlas_source->get_tile_data(coords, alternative);
298 ERR_FAIL_NULL_V(tile_data, false);
299 tile_data->set(p_name, p_value, &valid);
300
301 any_valid |= valid;
302 }
303
304 if (any_valid) {
305 emit_signal(SNAME("changed"), String(p_name).utf8().get_data());
306 }
307
308 return any_valid;
309}
310
311bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_name, Variant &r_ret) const {
312 if (!tile_set_atlas_source.is_valid()) {
313 return false;
314 }
315
316 // ID and size related properties.s
317 if (tiles.size() == 1) {
318 const Vector2i &coords = tiles.front()->get().tile;
319 const int &alternative = tiles.front()->get().alternative;
320
321 if (alternative == 0) {
322 Vector<String> components = String(p_name).split("/", true, 2);
323 if (p_name == "atlas_coords") {
324 r_ret = coords;
325 return true;
326 } else if (p_name == "size_in_atlas") {
327 r_ret = tile_set_atlas_source->get_tile_size_in_atlas(coords);
328 return true;
329 }
330 } else if (alternative > 0) {
331 if (p_name == "alternative_id") {
332 r_ret = alternative;
333 return true;
334 }
335 }
336 }
337
338 // Animation.
339 // Check if all tiles have an alternative_id of 0.
340 bool all_alternatve_id_zero = true;
341 for (TileSelection tile : tiles) {
342 if (tile.alternative != 0) {
343 all_alternatve_id_zero = false;
344 break;
345 }
346 }
347
348 if (all_alternatve_id_zero) {
349 const Vector2i &coords = tiles.front()->get().tile;
350
351 Vector<String> components = String(p_name).split("/", true, 2);
352 if (p_name == "animation_columns") {
353 r_ret = tile_set_atlas_source->get_tile_animation_columns(coords);
354 return true;
355 } else if (p_name == "animation_separation") {
356 r_ret = tile_set_atlas_source->get_tile_animation_separation(coords);
357 return true;
358 } else if (p_name == "animation_speed") {
359 r_ret = tile_set_atlas_source->get_tile_animation_speed(coords);
360 return true;
361 } else if (p_name == "animation_mode") {
362 r_ret = tile_set_atlas_source->get_tile_animation_mode(coords);
363 return true;
364 } else if (p_name == "animation_frames_count") {
365 r_ret = tile_set_atlas_source->get_tile_animation_frames_count(coords);
366 return true;
367 } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) {
368 int frame = components[0].trim_prefix("animation_frame_").to_int();
369 if (components[1] == "duration") {
370 if (frame < 0 || frame >= tile_set_atlas_source->get_tile_animation_frames_count(coords)) {
371 return false;
372 }
373 r_ret = tile_set_atlas_source->get_tile_animation_frame_duration(coords, frame);
374 return true;
375 }
376 }
377 }
378
379 for (const TileSelection &E : tiles) {
380 // Return the first tile with a property matching the name.
381 // Note: It's a little bit annoying, but the behavior is the same the one in MultiNodeEdit.
382 const Vector2i &coords = E.tile;
383 const int &alternative = E.alternative;
384
385 TileData *tile_data = tile_set_atlas_source->get_tile_data(coords, alternative);
386 ERR_FAIL_NULL_V(tile_data, false);
387
388 bool valid = false;
389 r_ret = tile_data->get(p_name, &valid);
390 if (valid) {
391 return true;
392 }
393 }
394
395 return false;
396}
397
398void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<PropertyInfo> *p_list) const {
399 if (!tile_set_atlas_source.is_valid()) {
400 return;
401 }
402
403 // ID and size related properties.
404 if (tiles.size() == 1) {
405 if (tiles.front()->get().alternative == 0) {
406 p_list->push_back(PropertyInfo(Variant::NIL, TTR("Base Tile"), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY));
407 p_list->push_back(PropertyInfo(Variant::VECTOR2I, PNAME("atlas_coords")));
408 p_list->push_back(PropertyInfo(Variant::VECTOR2I, PNAME("size_in_atlas")));
409 } else {
410 p_list->push_back(PropertyInfo(Variant::NIL, TTR("Alternative Tile"), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY));
411 p_list->push_back(PropertyInfo(Variant::INT, PNAME("alternative_id")));
412 }
413 } else {
414 p_list->push_back(PropertyInfo(Variant::NIL, TTR("Tiles"), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY));
415 }
416
417 // Animation.
418 // Check if all tiles have an alternative_id of 0.
419 bool all_alternatve_id_zero = true;
420 for (TileSelection tile : tiles) {
421 if (tile.alternative != 0) {
422 all_alternatve_id_zero = false;
423 break;
424 }
425 }
426
427 if (all_alternatve_id_zero) {
428 p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Animation", "animation_"), PROPERTY_HINT_NONE, "animation_", PROPERTY_USAGE_GROUP));
429 p_list->push_back(PropertyInfo(Variant::INT, PNAME("animation_columns")));
430 p_list->push_back(PropertyInfo(Variant::VECTOR2I, PNAME("animation_separation")));
431 p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("animation_speed")));
432 p_list->push_back(PropertyInfo(Variant::INT, PNAME("animation_mode"), PROPERTY_HINT_ENUM, "Default,Random Start Times"));
433 p_list->push_back(PropertyInfo(Variant::INT, PNAME("animation_frames_count"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Frames,animation_frame_"));
434 // Not optimal, but returns value for the first tile. This is similar to what MultiNodeEdit does.
435 if (tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile) == 1) {
436 p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_frame_0/duration", PROPERTY_HINT_NONE, "suffix:s", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY));
437 } else {
438 for (int i = 0; i < tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile); i++) {
439 p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("animation_frame_%d/%s", i, PNAME("duration")), PROPERTY_HINT_NONE, "suffix:s"));
440 }
441 }
442 }
443
444 // Get the list of properties common to all tiles (similar to what's done in MultiNodeEdit).
445 struct PropertyId {
446 int occurence_id = 0;
447 String property;
448 bool operator<(const PropertyId &p_other) const {
449 return occurence_id == p_other.occurence_id ? property < p_other.property : occurence_id < p_other.occurence_id;
450 }
451 };
452 struct PLData {
453 int uses = 0;
454 PropertyInfo property_info;
455 };
456 RBMap<PropertyId, PLData> usage;
457
458 List<PLData *> data_list;
459 for (const TileSelection &E : tiles) {
460 const Vector2i &coords = E.tile;
461 const int &alternative = E.alternative;
462
463 TileData *tile_data = tile_set_atlas_source->get_tile_data(coords, alternative);
464 ERR_FAIL_NULL(tile_data);
465
466 List<PropertyInfo> list;
467 tile_data->get_property_list(&list);
468
469 HashMap<String, int> counts; // Counts the number of time a property appears (useful for groups that may appear more than once)
470 for (List<PropertyInfo>::Element *E_property = list.front(); E_property; E_property = E_property->next()) {
471 // Don't show category for TileData.
472 if (E_property->get().usage & PROPERTY_USAGE_CATEGORY) {
473 continue;
474 }
475
476 const String &property_string = E_property->get().name;
477 if (!tile_data->is_allowing_transform() && (property_string == "flip_h" || property_string == "flip_v" || property_string == "transpose")) {
478 continue;
479 }
480
481 if (!counts.has(property_string)) {
482 counts[property_string] = 1;
483 } else {
484 counts[property_string] += 1;
485 }
486
487 PropertyInfo stored_property_info = E_property->get();
488 stored_property_info.usage |= PROPERTY_USAGE_STORAGE; // Ignore the storage flag in comparing properties.
489
490 PropertyId id = { counts[property_string], property_string };
491 if (!usage.has(id)) {
492 usage[id] = { 1, stored_property_info };
493 data_list.push_back(&usage[id]);
494 } else if (usage[id].property_info == stored_property_info) {
495 usage[id].uses += 1;
496 }
497 }
498 }
499
500 // Add only properties that are common to all tiles.
501 for (const PLData *E : data_list) {
502 if (E->uses == tiles.size()) {
503 p_list->push_back(E->property_info);
504 }
505 }
506}
507
508void TileSetAtlasSourceEditor::AtlasTileProxyObject::edit(Ref<TileSetAtlasSource> p_tile_set_atlas_source, RBSet<TileSelection> p_tiles) {
509 ERR_FAIL_COND(!p_tile_set_atlas_source.is_valid());
510 ERR_FAIL_COND(p_tiles.is_empty());
511 for (const TileSelection &E : p_tiles) {
512 ERR_FAIL_COND(E.tile == TileSetSource::INVALID_ATLAS_COORDS);
513 ERR_FAIL_COND(E.alternative < 0);
514 }
515
516 // Disconnect to changes.
517 for (const TileSelection &E : tiles) {
518 const Vector2i &coords = E.tile;
519 const int &alternative = E.alternative;
520
521 if (tile_set_atlas_source.is_valid() && tile_set_atlas_source->has_tile(coords) && tile_set_atlas_source->has_alternative_tile(coords, alternative)) {
522 TileData *tile_data = tile_set_atlas_source->get_tile_data(coords, alternative);
523 if (tile_data->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) {
524 tile_data->disconnect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed));
525 }
526 }
527 }
528
529 tile_set_atlas_source = p_tile_set_atlas_source;
530 tiles = RBSet<TileSelection>(p_tiles);
531
532 // Connect to changes.
533 for (const TileSelection &E : p_tiles) {
534 const Vector2i &coords = E.tile;
535 const int &alternative = E.alternative;
536
537 if (tile_set_atlas_source->has_tile(coords) && tile_set_atlas_source->has_alternative_tile(coords, alternative)) {
538 TileData *tile_data = tile_set_atlas_source->get_tile_data(coords, alternative);
539 if (!tile_data->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) {
540 tile_data->connect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed));
541 }
542 }
543 }
544
545 notify_property_list_changed();
546}
547
548void TileSetAtlasSourceEditor::AtlasTileProxyObject::_bind_methods() {
549 ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what")));
550}
551
552void TileSetAtlasSourceEditor::_inspector_property_selected(String p_property) {
553 selected_property = p_property;
554 _update_atlas_view();
555 _update_current_tile_data_editor();
556}
557
558void TileSetAtlasSourceEditor::_update_tile_id_label() {
559 if (selection.size() == 1) {
560 TileSelection selected = selection.front()->get();
561 tool_tile_id_label->set_text(vformat("%d, %s, %d", tile_set_atlas_source_id, selected.tile, selected.alternative));
562 tool_tile_id_label->set_tooltip_text(vformat(TTR("Selected tile:\nSource: %d\nAtlas coordinates: %s\nAlternative: %d"), tile_set_atlas_source_id, selected.tile, selected.alternative));
563 tool_tile_id_label->show();
564 } else {
565 tool_tile_id_label->hide();
566 }
567}
568
569void TileSetAtlasSourceEditor::_update_source_inspector() {
570 // Update the proxy object.
571 atlas_source_proxy_object->edit(tile_set, tile_set_atlas_source, tile_set_atlas_source_id);
572}
573
574void TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles() {
575 // Fix selected.
576 for (RBSet<TileSelection>::Element *E = selection.front(); E;) {
577 RBSet<TileSelection>::Element *N = E->next();
578 TileSelection selected = E->get();
579 if (!tile_set_atlas_source->has_tile(selected.tile) || !tile_set_atlas_source->has_alternative_tile(selected.tile, selected.alternative)) {
580 selection.erase(E);
581 }
582 E = N;
583 }
584
585 // Fix hovered.
586 if (!tile_set_atlas_source->has_tile(hovered_base_tile_coords)) {
587 hovered_base_tile_coords = TileSetSource::INVALID_ATLAS_COORDS;
588 }
589 Vector2i coords = Vector2i(hovered_alternative_tile_coords.x, hovered_alternative_tile_coords.y);
590 int alternative = hovered_alternative_tile_coords.z;
591 if (!tile_set_atlas_source->has_tile(coords) || !tile_set_atlas_source->has_alternative_tile(coords, alternative)) {
592 hovered_alternative_tile_coords = Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE);
593 }
594}
595
596void TileSetAtlasSourceEditor::_update_atlas_source_inspector() {
597 // Update visibility.
598 bool inspector_visible = tools_button_group->get_pressed_button() == tool_setup_atlas_source_button;
599 atlas_source_inspector->set_visible(inspector_visible);
600 atlas_source_inspector->set_read_only(read_only);
601}
602
603void TileSetAtlasSourceEditor::_update_tile_inspector() {
604 // Update visibility.
605 if (tools_button_group->get_pressed_button() == tool_select_button) {
606 if (!selection.is_empty()) {
607 tile_proxy_object->edit(tile_set_atlas_source, selection);
608 }
609 tile_inspector->set_visible(!selection.is_empty());
610 tile_inspector_no_tile_selected_label->set_visible(selection.is_empty());
611 } else {
612 tile_inspector->hide();
613 tile_inspector_no_tile_selected_label->hide();
614 }
615 tile_inspector->set_read_only(read_only);
616}
617
618void TileSetAtlasSourceEditor::_update_tile_data_editors() {
619 String previously_selected;
620 if (tile_data_editors_tree && tile_data_editors_tree->get_selected()) {
621 previously_selected = tile_data_editors_tree->get_selected()->get_metadata(0);
622 }
623
624 tile_data_editors_tree->clear();
625
626 if (tile_set.is_null()) {
627 return;
628 }
629
630 TreeItem *root = tile_data_editors_tree->create_item();
631
632 TreeItem *group;
633#define ADD_TILE_DATA_EDITOR_GROUP(text) \
634 group = tile_data_editors_tree->create_item(root); \
635 group->set_custom_bg_color(0, group_color); \
636 group->set_selectable(0, false); \
637 group->set_disable_folding(true); \
638 group->set_text(0, text);
639
640 TreeItem *item;
641#define ADD_TILE_DATA_EDITOR(parent, text, property) \
642 item = tile_data_editors_tree->create_item(parent); \
643 item->set_text(0, text); \
644 item->set_metadata(0, property); \
645 if (property == previously_selected) { \
646 item->select(0); \
647 }
648
649 // Theming.
650 tile_data_editors_tree->add_theme_constant_override("v_separation", 1);
651 tile_data_editors_tree->add_theme_constant_override("h_separation", 3);
652
653 Color group_color = get_theme_color(SNAME("prop_category"), EditorStringName(Editor));
654
655 // List of editors.
656 // --- Rendering ---
657 ADD_TILE_DATA_EDITOR_GROUP(TTR("Rendering"));
658
659 ADD_TILE_DATA_EDITOR(group, TTR("Texture Origin"), "texture_origin");
660 if (!tile_data_editors.has("texture_origin")) {
661 TileDataTextureOriginEditor *tile_data_texture_origin_editor = memnew(TileDataTextureOriginEditor);
662 tile_data_texture_origin_editor->hide();
663 tile_data_texture_origin_editor->setup_property_editor(Variant::VECTOR2I, "texture_origin");
664 tile_data_texture_origin_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw));
665 tile_data_texture_origin_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw));
666 tile_data_editors["texture_origin"] = tile_data_texture_origin_editor;
667 }
668
669 ADD_TILE_DATA_EDITOR(group, TTR("Modulate"), "modulate");
670 if (!tile_data_editors.has("modulate")) {
671 TileDataDefaultEditor *tile_data_modulate_editor = memnew(TileDataDefaultEditor());
672 tile_data_modulate_editor->hide();
673 tile_data_modulate_editor->setup_property_editor(Variant::COLOR, "modulate", "", Color(1.0, 1.0, 1.0, 1.0));
674 tile_data_modulate_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw));
675 tile_data_modulate_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw));
676 tile_data_editors["modulate"] = tile_data_modulate_editor;
677 }
678
679 ADD_TILE_DATA_EDITOR(group, TTR("Z Index"), "z_index");
680 if (!tile_data_editors.has("z_index")) {
681 TileDataDefaultEditor *tile_data_z_index_editor = memnew(TileDataDefaultEditor());
682 tile_data_z_index_editor->hide();
683 tile_data_z_index_editor->setup_property_editor(Variant::INT, "z_index");
684 tile_data_z_index_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw));
685 tile_data_z_index_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw));
686 tile_data_editors["z_index"] = tile_data_z_index_editor;
687 }
688
689 ADD_TILE_DATA_EDITOR(group, TTR("Y Sort Origin"), "y_sort_origin");
690 if (!tile_data_editors.has("y_sort_origin")) {
691 TileDataYSortEditor *tile_data_y_sort_editor = memnew(TileDataYSortEditor);
692 tile_data_y_sort_editor->hide();
693 tile_data_y_sort_editor->setup_property_editor(Variant::INT, "y_sort_origin");
694 tile_data_y_sort_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw));
695 tile_data_y_sort_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw));
696 tile_data_editors["y_sort_origin"] = tile_data_y_sort_editor;
697 }
698
699 for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) {
700 ADD_TILE_DATA_EDITOR(group, vformat(TTR("Occlusion Layer %d"), i), vformat("occlusion_layer_%d", i));
701 if (!tile_data_editors.has(vformat("occlusion_layer_%d", i))) {
702 TileDataOcclusionShapeEditor *tile_data_occlusion_shape_editor = memnew(TileDataOcclusionShapeEditor());
703 tile_data_occlusion_shape_editor->hide();
704 tile_data_occlusion_shape_editor->set_occlusion_layer(i);
705 tile_data_occlusion_shape_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw));
706 tile_data_occlusion_shape_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw));
707 tile_data_editors[vformat("occlusion_layer_%d", i)] = tile_data_occlusion_shape_editor;
708 }
709 }
710 for (int i = tile_set->get_occlusion_layers_count(); tile_data_editors.has(vformat("occlusion_layer_%d", i)); i++) {
711 tile_data_editors[vformat("occlusion_layer_%d", i)]->queue_free();
712 tile_data_editors.erase(vformat("occlusion_layer_%d", i));
713 }
714
715 // --- Rendering ---
716 ADD_TILE_DATA_EDITOR(root, TTR("Terrains"), "terrain_set");
717 if (!tile_data_editors.has("terrain_set")) {
718 TileDataTerrainsEditor *tile_data_terrains_editor = memnew(TileDataTerrainsEditor);
719 tile_data_terrains_editor->hide();
720 tile_data_terrains_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw));
721 tile_data_terrains_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw));
722 tile_data_editors["terrain_set"] = tile_data_terrains_editor;
723 }
724
725 // --- Miscellaneous ---
726 ADD_TILE_DATA_EDITOR(root, TTR("Probability"), "probability");
727 if (!tile_data_editors.has("probability")) {
728 TileDataDefaultEditor *tile_data_probability_editor = memnew(TileDataDefaultEditor());
729 tile_data_probability_editor->hide();
730 tile_data_probability_editor->setup_property_editor(Variant::FLOAT, "probability", "", 1.0);
731 tile_data_probability_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw));
732 tile_data_probability_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw));
733 tile_data_editors["probability"] = tile_data_probability_editor;
734 }
735
736 Color disabled_color = get_theme_color("disabled_font_color", EditorStringName(Editor));
737
738 // --- Physics ---
739 ADD_TILE_DATA_EDITOR_GROUP(TTR("Physics"));
740 for (int i = 0; i < tile_set->get_physics_layers_count(); i++) {
741 ADD_TILE_DATA_EDITOR(group, vformat(TTR("Physics Layer %d"), i), vformat("physics_layer_%d", i));
742 if (!tile_data_editors.has(vformat("physics_layer_%d", i))) {
743 TileDataCollisionEditor *tile_data_collision_editor = memnew(TileDataCollisionEditor());
744 tile_data_collision_editor->hide();
745 tile_data_collision_editor->set_physics_layer(i);
746 tile_data_collision_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw));
747 tile_data_collision_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw));
748 tile_data_editors[vformat("physics_layer_%d", i)] = tile_data_collision_editor;
749 }
750 }
751 for (int i = tile_set->get_physics_layers_count(); tile_data_editors.has(vformat("physics_layer_%d", i)); i++) {
752 tile_data_editors[vformat("physics_layer_%d", i)]->queue_free();
753 tile_data_editors.erase(vformat("physics_layer_%d", i));
754 }
755
756 if (tile_set->get_physics_layers_count() == 0) {
757 item = tile_data_editors_tree->create_item(group);
758 item->set_icon(0, get_editor_theme_icon("Info"));
759 item->set_icon_modulate(0, disabled_color);
760 item->set_text(0, TTR("No physics layers"));
761 item->set_tooltip_text(0, TTR("Create and customize physics layers in the inspector of the TileSet resource."));
762 item->set_selectable(0, false);
763 item->set_custom_color(0, disabled_color);
764 }
765
766 // --- Navigation ---
767 ADD_TILE_DATA_EDITOR_GROUP(TTR("Navigation"));
768 for (int i = 0; i < tile_set->get_navigation_layers_count(); i++) {
769 ADD_TILE_DATA_EDITOR(group, vformat(TTR("Navigation Layer %d"), i), vformat("navigation_layer_%d", i));
770 if (!tile_data_editors.has(vformat("navigation_layer_%d", i))) {
771 TileDataNavigationEditor *tile_data_navigation_editor = memnew(TileDataNavigationEditor());
772 tile_data_navigation_editor->hide();
773 tile_data_navigation_editor->set_navigation_layer(i);
774 tile_data_navigation_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw));
775 tile_data_navigation_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw));
776 tile_data_editors[vformat("navigation_layer_%d", i)] = tile_data_navigation_editor;
777 }
778 }
779 for (int i = tile_set->get_navigation_layers_count(); tile_data_editors.has(vformat("navigation_layer_%d", i)); i++) {
780 tile_data_editors[vformat("navigation_layer_%d", i)]->queue_free();
781 tile_data_editors.erase(vformat("navigation_layer_%d", i));
782 }
783
784 if (tile_set->get_navigation_layers_count() == 0) {
785 item = tile_data_editors_tree->create_item(group);
786 item->set_icon(0, get_editor_theme_icon("Info"));
787 item->set_icon_modulate(0, disabled_color);
788 item->set_text(0, TTR("No navigation layers"));
789 item->set_tooltip_text(0, TTR("Create and customize navigation layers in the inspector of the TileSet resource."));
790 item->set_selectable(0, false);
791 item->set_custom_color(0, disabled_color);
792 }
793
794 // --- Custom Data ---
795 ADD_TILE_DATA_EDITOR_GROUP(TTR("Custom Data"));
796 for (int i = 0; i < tile_set->get_custom_data_layers_count(); i++) {
797 String editor_name = vformat("custom_data_%d", i);
798 String prop_name = tile_set->get_custom_data_layer_name(i);
799 Variant::Type prop_type = tile_set->get_custom_data_layer_type(i);
800
801 if (prop_name.is_empty()) {
802 ADD_TILE_DATA_EDITOR(group, vformat(TTR("Custom Data %d"), i), editor_name);
803 } else {
804 ADD_TILE_DATA_EDITOR(group, prop_name, editor_name);
805 }
806
807 // If the type of the edited property has been changed, delete the
808 // editor and create a new one.
809 if (tile_data_editors.has(editor_name) && ((TileDataDefaultEditor *)tile_data_editors[editor_name])->get_property_type() != prop_type) {
810 tile_data_editors[vformat("custom_data_%d", i)]->queue_free();
811 tile_data_editors.erase(vformat("custom_data_%d", i));
812 }
813 if (!tile_data_editors.has(editor_name)) {
814 TileDataDefaultEditor *tile_data_custom_data_editor = memnew(TileDataDefaultEditor());
815 tile_data_custom_data_editor->hide();
816 tile_data_custom_data_editor->setup_property_editor(prop_type, editor_name, prop_name);
817 tile_data_custom_data_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw));
818 tile_data_custom_data_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw));
819 tile_data_editors[editor_name] = tile_data_custom_data_editor;
820 }
821 }
822 for (int i = tile_set->get_custom_data_layers_count(); tile_data_editors.has(vformat("custom_data_%d", i)); i++) {
823 tile_data_editors[vformat("custom_data_%d", i)]->queue_free();
824 tile_data_editors.erase(vformat("custom_data_%d", i));
825 }
826
827 if (tile_set->get_custom_data_layers_count() == 0) {
828 item = tile_data_editors_tree->create_item(group);
829 item->set_icon(0, get_editor_theme_icon("Info"));
830 item->set_icon_modulate(0, disabled_color);
831 item->set_text(0, TTR("No custom data layers"));
832 item->set_tooltip_text(0, TTR("Create and customize custom data layers in the inspector of the TileSet resource."));
833 item->set_selectable(0, false);
834 item->set_custom_color(0, disabled_color);
835 }
836
837#undef ADD_TILE_DATA_EDITOR_GROUP
838#undef ADD_TILE_DATA_EDITOR
839
840 // Add tile data editors as children.
841 for (KeyValue<String, TileDataEditor *> &E : tile_data_editors) {
842 // Tile Data Editor.
843 TileDataEditor *tile_data_editor = E.value;
844 if (!tile_data_editor->is_inside_tree()) {
845 tile_data_painting_editor_container->add_child(tile_data_editor);
846 }
847 tile_data_editor->set_tile_set(tile_set);
848
849 // Toolbar.
850 Control *toolbar = tile_data_editor->get_toolbar();
851 if (!toolbar->is_inside_tree()) {
852 tool_settings_tile_data_toolbar_container->add_child(toolbar);
853 }
854 toolbar->hide();
855 }
856
857 // Update visibility.
858 bool is_visible = tools_button_group->get_pressed_button() == tool_paint_button;
859 tile_data_editor_dropdown_button->set_visible(is_visible);
860 if (tile_data_editors_tree->get_selected()) {
861 tile_data_editor_dropdown_button->set_text(tile_data_editors_tree->get_selected()->get_text(0));
862 } else {
863 tile_data_editor_dropdown_button->set_text(TTR("Select a property editor"));
864 }
865 tile_data_editors_scroll->set_visible(is_visible);
866}
867
868void TileSetAtlasSourceEditor::_update_current_tile_data_editor() {
869 // Find the property to use.
870 String property;
871 if (tools_button_group->get_pressed_button() == tool_select_button && tile_inspector->is_visible() && !tile_inspector->get_selected_path().is_empty()) {
872 Vector<String> components = tile_inspector->get_selected_path().split("/");
873 if (components.size() >= 1) {
874 property = components[0];
875
876 // Workaround for terrains as they don't have a common first component.
877 if (property.begins_with("terrains_")) {
878 property = "terrain_set";
879 }
880 }
881 } else if (tools_button_group->get_pressed_button() == tool_paint_button && tile_data_editors_tree->get_selected()) {
882 property = tile_data_editors_tree->get_selected()->get_metadata(0);
883 tile_data_editor_dropdown_button->set_text(tile_data_editors_tree->get_selected()->get_text(0));
884 }
885
886 // Hide all editors but the current one.
887 for (const KeyValue<String, TileDataEditor *> &E : tile_data_editors) {
888 E.value->hide();
889 E.value->get_toolbar()->hide();
890 }
891 if (tile_data_editors.has(property)) {
892 current_tile_data_editor = tile_data_editors[property];
893 } else {
894 current_tile_data_editor = nullptr;
895 }
896
897 // Get the correct editor for the TileData's property.
898 if (current_tile_data_editor) {
899 current_tile_data_editor_toolbar = current_tile_data_editor->get_toolbar();
900 current_property = property;
901 current_tile_data_editor->set_visible(tools_button_group->get_pressed_button() == tool_paint_button);
902 current_tile_data_editor_toolbar->set_visible(tools_button_group->get_pressed_button() == tool_paint_button);
903 }
904}
905
906void TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_draw() {
907 if (!has_theme_icon(SNAME("arrow"), SNAME("OptionButton"))) {
908 return;
909 }
910
911 RID ci = tile_data_editor_dropdown_button->get_canvas_item();
912 Ref<Texture2D> arrow = Control::get_theme_icon(SNAME("arrow"), SNAME("OptionButton"));
913 Color clr = Color(1, 1, 1);
914 if (get_theme_constant(SNAME("modulate_arrow"))) {
915 switch (tile_data_editor_dropdown_button->get_draw_mode()) {
916 case BaseButton::DRAW_PRESSED:
917 clr = get_theme_color(SNAME("font_pressed_color"));
918 break;
919 case BaseButton::DRAW_HOVER:
920 clr = get_theme_color(SNAME("font_hover_color"));
921 break;
922 case BaseButton::DRAW_DISABLED:
923 clr = get_theme_color(SNAME("font_disabled_color"));
924 break;
925 default:
926 if (tile_data_editor_dropdown_button->has_focus()) {
927 clr = get_theme_color(SNAME("font_focus_color"));
928 } else {
929 clr = get_theme_color(SNAME("font_color"));
930 }
931 }
932 }
933
934 Size2 size = tile_data_editor_dropdown_button->get_size();
935
936 Point2 ofs;
937 if (is_layout_rtl()) {
938 ofs = Point2(get_theme_constant(SNAME("arrow_margin"), SNAME("OptionButton")), int(Math::abs((size.height - arrow->get_height()) / 2)));
939 } else {
940 ofs = Point2(size.width - arrow->get_width() - get_theme_constant(SNAME("arrow_margin"), SNAME("OptionButton")), int(Math::abs((size.height - arrow->get_height()) / 2)));
941 }
942 arrow->draw(ci, ofs, clr);
943}
944
945void TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_pressed() {
946 Size2 size = tile_data_editor_dropdown_button->get_size();
947 tile_data_editors_popup->set_position(tile_data_editor_dropdown_button->get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y));
948 tile_data_editors_popup->set_size(Size2(size.width, 0));
949 tile_data_editors_popup->popup();
950}
951
952void TileSetAtlasSourceEditor::_tile_data_editors_tree_selected() {
953 tile_data_editors_popup->call_deferred(SNAME("hide"));
954 _update_current_tile_data_editor();
955 tile_atlas_control->queue_redraw();
956 tile_atlas_control_unscaled->queue_redraw();
957 alternative_tiles_control->queue_redraw();
958 alternative_tiles_control_unscaled->queue_redraw();
959}
960
961void TileSetAtlasSourceEditor::_update_atlas_view() {
962 // Update the atlas display.
963 tile_atlas_view->set_atlas_source(*tile_set, tile_set_atlas_source, tile_set_atlas_source_id);
964
965 // Create a bunch of buttons to add alternative tiles.
966 for (int i = 0; i < alternative_tiles_control->get_child_count(); i++) {
967 alternative_tiles_control->get_child(i)->queue_free();
968 }
969
970 if (tile_set.is_null()) {
971 return;
972 } else {
973 tile_create_help->set_visible(tools_button_group->get_pressed_button() == tool_setup_atlas_source_button);
974 }
975
976 Vector2i pos;
977 Vector2 texture_region_base_size = tile_set_atlas_source->get_texture_region_size();
978 int texture_region_base_size_min = MIN(texture_region_base_size.x, texture_region_base_size.y);
979 for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
980 Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
981 int alternative_count = tile_set_atlas_source->get_alternative_tiles_count(tile_id);
982 if (alternative_count > 1) {
983 // Compute the right extremity of alternative.
984 int y_increment = 0;
985 pos.x = 0;
986 for (int j = 1; j < alternative_count; j++) {
987 int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j);
988 Rect2i rect = tile_atlas_view->get_alternative_tile_rect(tile_id, alternative_id);
989 pos.x = MAX(pos.x, rect.get_end().x);
990 y_increment = MAX(y_increment, rect.size.y);
991 }
992
993 // Create and position the button.
994 Button *button = memnew(Button);
995 button->set_flat(true);
996 button->set_icon(get_editor_theme_icon(SNAME("Add")));
997 button->add_theme_style_override("normal", memnew(StyleBoxEmpty));
998 button->add_theme_style_override("hover", memnew(StyleBoxEmpty));
999 button->add_theme_style_override("focus", memnew(StyleBoxEmpty));
1000 button->add_theme_style_override("pressed", memnew(StyleBoxEmpty));
1001 button->connect("pressed", callable_mp(tile_set_atlas_source, &TileSetAtlasSource::create_alternative_tile).bind(tile_id, TileSetSource::INVALID_TILE_ALTERNATIVE));
1002 button->set_rect(Rect2(Vector2(pos.x, pos.y + (y_increment - texture_region_base_size.y) / 2.0), Vector2(texture_region_base_size_min, texture_region_base_size_min)));
1003 button->set_expand_icon(true);
1004 alternative_tiles_control->add_child(button);
1005
1006 pos.y += y_increment;
1007 }
1008 }
1009 tile_atlas_view->set_padding(Side::SIDE_RIGHT, texture_region_base_size_min);
1010
1011 // Redraw everything.
1012 tile_atlas_control->queue_redraw();
1013 tile_atlas_control_unscaled->queue_redraw();
1014 alternative_tiles_control->queue_redraw();
1015 alternative_tiles_control_unscaled->queue_redraw();
1016 tile_atlas_view->queue_redraw();
1017
1018 // Synchronize atlas view.
1019 TilesEditorUtils::get_singleton()->synchronize_atlas_view(tile_atlas_view);
1020}
1021
1022void TileSetAtlasSourceEditor::_update_toolbar() {
1023 // Show the tools and settings.
1024 if (tools_button_group->get_pressed_button() == tool_setup_atlas_source_button) {
1025 if (current_tile_data_editor_toolbar) {
1026 current_tile_data_editor_toolbar->hide();
1027 }
1028 tools_settings_erase_button->show();
1029 tool_advanced_menu_button->show();
1030 } else if (tools_button_group->get_pressed_button() == tool_select_button) {
1031 if (current_tile_data_editor_toolbar) {
1032 current_tile_data_editor_toolbar->hide();
1033 }
1034 tools_settings_erase_button->hide();
1035 tool_advanced_menu_button->hide();
1036 } else if (tools_button_group->get_pressed_button() == tool_paint_button) {
1037 if (current_tile_data_editor_toolbar) {
1038 current_tile_data_editor_toolbar->show();
1039 }
1040 tools_settings_erase_button->hide();
1041 tool_advanced_menu_button->hide();
1042 }
1043}
1044
1045void TileSetAtlasSourceEditor::_tile_atlas_control_mouse_exited() {
1046 hovered_base_tile_coords = TileSetSource::INVALID_ATLAS_COORDS;
1047 tile_atlas_control->queue_redraw();
1048 tile_atlas_control_unscaled->queue_redraw();
1049 tile_atlas_view->queue_redraw();
1050}
1051
1052void TileSetAtlasSourceEditor::_tile_atlas_view_transform_changed() {
1053 tile_atlas_control->queue_redraw();
1054 tile_atlas_control_unscaled->queue_redraw();
1055}
1056
1057void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEvent> &p_event) {
1058 // Update the hovered coords.
1059 hovered_base_tile_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
1060
1061 // Forward the event to the current tile data editor if we are in the painting mode.
1062 if (tools_button_group->get_pressed_button() == tool_paint_button) {
1063 if (current_tile_data_editor) {
1064 current_tile_data_editor->forward_painting_atlas_gui_input(tile_atlas_view, tile_set_atlas_source, p_event);
1065 }
1066 // Update only what's needed.
1067 tile_set_changed_needs_update = false;
1068
1069 tile_atlas_control->queue_redraw();
1070 tile_atlas_control_unscaled->queue_redraw();
1071 alternative_tiles_control->queue_redraw();
1072 alternative_tiles_control_unscaled->queue_redraw();
1073 tile_atlas_view->queue_redraw();
1074 return;
1075 } else {
1076 // Handle the event.
1077 Ref<InputEventMouseMotion> mm = p_event;
1078 if (mm.is_valid()) {
1079 Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos, true);
1080 Vector2i last_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_mouse_pos, true);
1081 Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
1082
1083 Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
1084
1085 if (drag_type == DRAG_TYPE_CREATE_BIG_TILE) {
1086 // Create big tile.
1087 new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1));
1088
1089 Rect2i new_rect = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
1090 new_rect.size += Vector2i(1, 1);
1091 // Check if the new tile can fit in the new rect.
1092 if (tile_set_atlas_source->has_room_for_tile(new_rect.position, new_rect.size, tile_set_atlas_source->get_tile_animation_columns(drag_current_tile), tile_set_atlas_source->get_tile_animation_separation(drag_current_tile), tile_set_atlas_source->get_tile_animation_frames_count(drag_current_tile), drag_current_tile)) {
1093 // Move and resize the tile.
1094 tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size);
1095 drag_current_tile = new_rect.position;
1096 }
1097 } else if (drag_type == DRAG_TYPE_CREATE_TILES) {
1098 // Create tiles.
1099 last_base_tiles_coords = last_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1));
1100 new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1));
1101
1102 Vector<Point2i> line = Geometry2D::bresenham_line(last_base_tiles_coords, new_base_tiles_coords);
1103 for (int i = 0; i < line.size(); i++) {
1104 if (tile_set_atlas_source->get_tile_at_coords(line[i]) == TileSetSource::INVALID_ATLAS_COORDS) {
1105 tile_set_atlas_source->create_tile(line[i]);
1106 drag_modified_tiles.insert(line[i]);
1107 }
1108 }
1109
1110 drag_last_mouse_pos = tile_atlas_control->get_local_mouse_position();
1111
1112 } else if (drag_type == DRAG_TYPE_REMOVE_TILES) {
1113 // Remove tiles.
1114 last_base_tiles_coords = last_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1));
1115 new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1));
1116
1117 Vector<Point2i> line = Geometry2D::bresenham_line(last_base_tiles_coords, new_base_tiles_coords);
1118 for (int i = 0; i < line.size(); i++) {
1119 Vector2i base_tile_coords = tile_set_atlas_source->get_tile_at_coords(line[i]);
1120 if (base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) {
1121 drag_modified_tiles.insert(base_tile_coords);
1122 }
1123 }
1124
1125 drag_last_mouse_pos = tile_atlas_control->get_local_mouse_position();
1126 } else if (drag_type == DRAG_TYPE_MOVE_TILE) {
1127 // Move tile.
1128 Vector2 mouse_offset = (Vector2(tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile)) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size();
1129 Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position() - mouse_offset);
1130 if (drag_current_tile != coords && tile_set_atlas_source->has_room_for_tile(coords, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile), tile_set_atlas_source->get_tile_animation_columns(drag_current_tile), tile_set_atlas_source->get_tile_animation_separation(drag_current_tile), tile_set_atlas_source->get_tile_animation_frames_count(drag_current_tile), drag_current_tile)) {
1131 tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, coords);
1132 selection.clear();
1133 selection.insert({ coords, 0 });
1134 drag_current_tile = coords;
1135
1136 // Update only what's needed.
1137 tile_set_changed_needs_update = false;
1138 _update_tile_inspector();
1139 _update_atlas_view();
1140 _update_tile_id_label();
1141 _update_current_tile_data_editor();
1142 }
1143 } else if (drag_type == DRAG_TYPE_MAY_POPUP_MENU) {
1144 if (Vector2(drag_start_mouse_pos).distance_to(tile_atlas_control->get_local_mouse_position()) > 5.0 * EDSCALE) {
1145 drag_type = DRAG_TYPE_NONE;
1146 }
1147 } else if (drag_type >= DRAG_TYPE_RESIZE_TOP_LEFT && drag_type <= DRAG_TYPE_RESIZE_LEFT) {
1148 // Resizing a tile.
1149 Rect2i old_rect = Rect2i(drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile));
1150 Rect2i new_rect = old_rect;
1151
1152 if (drag_type == DRAG_TYPE_RESIZE_LEFT || drag_type == DRAG_TYPE_RESIZE_TOP_LEFT || drag_type == DRAG_TYPE_RESIZE_BOTTOM_LEFT) {
1153 new_base_tiles_coords = _get_drag_offset_tile_coords(Vector2i(-1, 0));
1154 new_rect.position.x = MIN(new_base_tiles_coords.x + 1, old_rect.get_end().x - 1);
1155 new_rect.size.x = old_rect.get_end().x - new_rect.position.x;
1156 }
1157 if (drag_type == DRAG_TYPE_RESIZE_TOP || drag_type == DRAG_TYPE_RESIZE_TOP_LEFT || drag_type == DRAG_TYPE_RESIZE_TOP_RIGHT) {
1158 new_base_tiles_coords = _get_drag_offset_tile_coords(Vector2i(0, -1));
1159 new_rect.position.y = MIN(new_base_tiles_coords.y + 1, old_rect.get_end().y - 1);
1160 new_rect.size.y = old_rect.get_end().y - new_rect.position.y;
1161 }
1162
1163 if (drag_type == DRAG_TYPE_RESIZE_RIGHT || drag_type == DRAG_TYPE_RESIZE_TOP_RIGHT || drag_type == DRAG_TYPE_RESIZE_BOTTOM_RIGHT) {
1164 new_base_tiles_coords = _get_drag_offset_tile_coords(Vector2i(1, 0));
1165 new_rect.set_end(Vector2i(MAX(new_base_tiles_coords.x, old_rect.position.x + 1), new_rect.get_end().y));
1166 }
1167 if (drag_type == DRAG_TYPE_RESIZE_BOTTOM || drag_type == DRAG_TYPE_RESIZE_BOTTOM_LEFT || drag_type == DRAG_TYPE_RESIZE_BOTTOM_RIGHT) {
1168 new_base_tiles_coords = _get_drag_offset_tile_coords(Vector2i(0, 1));
1169 new_rect.set_end(Vector2i(new_rect.get_end().x, MAX(new_base_tiles_coords.y, old_rect.position.y + 1)));
1170 }
1171
1172 if (tile_set_atlas_source->has_room_for_tile(new_rect.position, new_rect.size, tile_set_atlas_source->get_tile_animation_columns(drag_current_tile), tile_set_atlas_source->get_tile_animation_separation(drag_current_tile), tile_set_atlas_source->get_tile_animation_frames_count(drag_current_tile), drag_current_tile)) {
1173 tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size);
1174 selection.clear();
1175 selection.insert({ new_rect.position, 0 });
1176 drag_current_tile = new_rect.position;
1177
1178 // Update only what's needed.
1179 tile_set_changed_needs_update = false;
1180 _update_tile_inspector();
1181 _update_atlas_view();
1182 _update_tile_id_label();
1183 _update_current_tile_data_editor();
1184 }
1185 }
1186
1187 // Redraw for the hovered tile.
1188 tile_atlas_control->queue_redraw();
1189 tile_atlas_control_unscaled->queue_redraw();
1190 alternative_tiles_control->queue_redraw();
1191 alternative_tiles_control_unscaled->queue_redraw();
1192 tile_atlas_view->queue_redraw();
1193 return;
1194 }
1195
1196 Ref<InputEventMouseButton> mb = p_event;
1197 if (mb.is_valid()) {
1198 Vector2 mouse_local_pos = tile_atlas_control->get_local_mouse_position();
1199 if (mb->get_button_index() == MouseButton::LEFT) {
1200 if (mb->is_pressed()) {
1201 // Left click pressed.
1202 if (tools_button_group->get_pressed_button() == tool_setup_atlas_source_button) {
1203 if (tools_settings_erase_button->is_pressed()) {
1204 // Erasing
1205 if (mb->is_ctrl_pressed() || mb->is_shift_pressed()) {
1206 // Remove tiles using rect.
1207
1208 // Setup the dragging info.
1209 drag_type = DRAG_TYPE_REMOVE_TILES_USING_RECT;
1210 drag_start_mouse_pos = mouse_local_pos;
1211 drag_last_mouse_pos = drag_start_mouse_pos;
1212 } else {
1213 // Remove tiles.
1214
1215 // Setup the dragging info.
1216 drag_type = DRAG_TYPE_REMOVE_TILES;
1217 drag_start_mouse_pos = mouse_local_pos;
1218 drag_last_mouse_pos = drag_start_mouse_pos;
1219
1220 // Remove a first tile.
1221 Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos);
1222 if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
1223 coords = tile_set_atlas_source->get_tile_at_coords(coords);
1224 }
1225 if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
1226 drag_modified_tiles.insert(coords);
1227 }
1228 }
1229 } else {
1230 // Creating
1231 if (mb->is_shift_pressed()) {
1232 // Create a big tile.
1233 Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos);
1234 if (coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
1235 // Setup the dragging info, only if we start on an empty tile.
1236 drag_type = DRAG_TYPE_CREATE_BIG_TILE;
1237 drag_start_mouse_pos = mouse_local_pos;
1238 drag_last_mouse_pos = drag_start_mouse_pos;
1239 drag_current_tile = coords;
1240
1241 // Create a tile.
1242 tile_set_atlas_source->create_tile(coords);
1243 }
1244 } else if (mb->is_ctrl_pressed()) {
1245 // Create tiles using rect.
1246 drag_type = DRAG_TYPE_CREATE_TILES_USING_RECT;
1247 drag_start_mouse_pos = mouse_local_pos;
1248 drag_last_mouse_pos = drag_start_mouse_pos;
1249 } else {
1250 // Create tiles.
1251
1252 // Setup the dragging info.
1253 drag_type = DRAG_TYPE_CREATE_TILES;
1254 drag_start_mouse_pos = mouse_local_pos;
1255 drag_last_mouse_pos = drag_start_mouse_pos;
1256
1257 // Create a first tile if needed.
1258 Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos);
1259 if (coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
1260 tile_set_atlas_source->create_tile(coords);
1261 drag_modified_tiles.insert(coords);
1262 }
1263 }
1264 }
1265 } else if (tools_button_group->get_pressed_button() == tool_select_button) {
1266 // Dragging a handle.
1267 drag_type = DRAG_TYPE_NONE;
1268 if (selection.size() == 1) {
1269 TileSelection selected = selection.front()->get();
1270 if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selected.alternative == 0) {
1271 Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile);
1272 Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile);
1273 Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom();
1274 Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0);
1275 const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) };
1276 const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) };
1277 bool can_grow[4];
1278 for (int i = 0; i < 4; i++) {
1279 can_grow[i] = tile_set_atlas_source->has_room_for_tile(selected.tile + directions[i], tile_set_atlas_source->get_tile_size_in_atlas(selected.tile), tile_set_atlas_source->get_tile_animation_columns(selected.tile), tile_set_atlas_source->get_tile_animation_separation(selected.tile), tile_set_atlas_source->get_tile_animation_frames_count(selected.tile), selected.tile);
1280 can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1;
1281 }
1282 for (int i = 0; i < 4; i++) {
1283 Vector2 pos = rect.position + rect.size * coords[i];
1284 if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) {
1285 drag_type = (DragType)((int)DRAG_TYPE_RESIZE_TOP_LEFT + i * 2);
1286 drag_start_mouse_pos = mouse_local_pos;
1287 drag_last_mouse_pos = drag_start_mouse_pos;
1288 drag_current_tile = selected.tile;
1289 drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile));
1290 }
1291 Vector2 next_pos = rect.position + rect.size * coords[(i + 1) % 4];
1292 if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) {
1293 drag_type = (DragType)((int)DRAG_TYPE_RESIZE_TOP + i * 2);
1294 drag_start_mouse_pos = mouse_local_pos;
1295 drag_last_mouse_pos = drag_start_mouse_pos;
1296 drag_current_tile = selected.tile;
1297 drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile));
1298 }
1299 }
1300 }
1301 }
1302
1303 // Selecting then dragging a tile.
1304 if (drag_type == DRAG_TYPE_NONE) {
1305 TileSelection selected = { TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE };
1306 Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos);
1307 if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
1308 coords = tile_set_atlas_source->get_tile_at_coords(coords);
1309 if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
1310 selected = { coords, 0 };
1311 }
1312 }
1313
1314 bool shift = mb->is_shift_pressed();
1315 if (!shift && selection.size() == 1 && selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selection.has(selected)) {
1316 // Start move dragging.
1317 drag_type = DRAG_TYPE_MOVE_TILE;
1318 drag_start_mouse_pos = mouse_local_pos;
1319 drag_last_mouse_pos = drag_start_mouse_pos;
1320 drag_current_tile = selected.tile;
1321 drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile));
1322 } else {
1323 // Start selection dragging.
1324 drag_type = DRAG_TYPE_RECT_SELECT;
1325 drag_start_mouse_pos = mouse_local_pos;
1326 drag_last_mouse_pos = drag_start_mouse_pos;
1327 }
1328 }
1329 }
1330 } else {
1331 // Left click released.
1332 _end_dragging();
1333 }
1334 tile_atlas_control->queue_redraw();
1335 tile_atlas_control_unscaled->queue_redraw();
1336 alternative_tiles_control->queue_redraw();
1337 alternative_tiles_control_unscaled->queue_redraw();
1338 tile_atlas_view->queue_redraw();
1339 return;
1340 } else if (mb->get_button_index() == MouseButton::RIGHT) {
1341 // Right click pressed.
1342 if (mb->is_pressed()) {
1343 drag_type = DRAG_TYPE_MAY_POPUP_MENU;
1344 drag_start_mouse_pos = tile_atlas_control->get_local_mouse_position();
1345 } else {
1346 // Right click released.
1347 _end_dragging();
1348 }
1349 tile_atlas_control->queue_redraw();
1350 tile_atlas_control_unscaled->queue_redraw();
1351 alternative_tiles_control->queue_redraw();
1352 alternative_tiles_control_unscaled->queue_redraw();
1353 tile_atlas_view->queue_redraw();
1354 return;
1355 }
1356 }
1357 }
1358}
1359
1360void TileSetAtlasSourceEditor::_end_dragging() {
1361 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1362 switch (drag_type) {
1363 case DRAG_TYPE_CREATE_TILES:
1364 undo_redo->create_action(TTR("Create tiles"));
1365 for (const Vector2i &E : drag_modified_tiles) {
1366 undo_redo->add_do_method(tile_set_atlas_source, "create_tile", E);
1367 undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", E);
1368 }
1369 undo_redo->commit_action(false);
1370 break;
1371 case DRAG_TYPE_CREATE_BIG_TILE:
1372 undo_redo->create_action(TTR("Create a tile"));
1373 undo_redo->add_do_method(tile_set_atlas_source, "create_tile", drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile));
1374 undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", drag_current_tile);
1375 undo_redo->commit_action(false);
1376 break;
1377 case DRAG_TYPE_REMOVE_TILES: {
1378 List<PropertyInfo> list;
1379 tile_set_atlas_source->get_property_list(&list);
1380 HashMap<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source);
1381 undo_redo->create_action(TTR("Remove tiles"));
1382 for (const Vector2i &E : drag_modified_tiles) {
1383 Vector2i coords = E;
1384 undo_redo->add_do_method(tile_set_atlas_source, "remove_tile", coords);
1385 undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", coords);
1386 if (per_tile.has(coords)) {
1387 for (List<const PropertyInfo *>::Element *E_property = per_tile[coords].front(); E_property; E_property = E_property->next()) {
1388 String property = E_property->get()->name;
1389 Variant value = tile_set_atlas_source->get(property);
1390 if (value.get_type() != Variant::NIL) {
1391 undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value);
1392 }
1393 }
1394 }
1395 }
1396 undo_redo->commit_action();
1397 } break;
1398 case DRAG_TYPE_CREATE_TILES_USING_RECT: {
1399 Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos, true);
1400 Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position(), true);
1401 Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
1402 area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size()));
1403 undo_redo->create_action(TTR("Create tiles"));
1404 for (int x = area.get_position().x; x < area.get_end().x; x++) {
1405 for (int y = area.get_position().y; y < area.get_end().y; y++) {
1406 Vector2i coords = Vector2i(x, y);
1407 if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
1408 undo_redo->add_do_method(tile_set_atlas_source, "create_tile", coords);
1409 undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", coords);
1410 }
1411 }
1412 }
1413 undo_redo->commit_action();
1414 } break;
1415 case DRAG_TYPE_REMOVE_TILES_USING_RECT: {
1416 Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos, true);
1417 Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position(), true);
1418 Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
1419 area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size()));
1420 List<PropertyInfo> list;
1421 tile_set_atlas_source->get_property_list(&list);
1422 HashMap<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source);
1423
1424 RBSet<Vector2i> to_delete;
1425 for (int x = area.get_position().x; x < area.get_end().x; x++) {
1426 for (int y = area.get_position().y; y < area.get_end().y; y++) {
1427 Vector2i coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y));
1428 if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
1429 to_delete.insert(coords);
1430 }
1431 }
1432 }
1433
1434 undo_redo->create_action(TTR("Remove tiles"));
1435 undo_redo->add_do_method(this, "_set_selection_from_array", Array());
1436 for (const Vector2i &E : to_delete) {
1437 Vector2i coords = E;
1438 undo_redo->add_do_method(tile_set_atlas_source, "remove_tile", coords);
1439 undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", coords);
1440 if (per_tile.has(coords)) {
1441 for (List<const PropertyInfo *>::Element *E_property = per_tile[coords].front(); E_property; E_property = E_property->next()) {
1442 String property = E_property->get()->name;
1443 Variant value = tile_set_atlas_source->get(property);
1444 if (value.get_type() != Variant::NIL) {
1445 undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value);
1446 }
1447 }
1448 }
1449 }
1450 undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array());
1451 undo_redo->commit_action();
1452 } break;
1453 case DRAG_TYPE_MOVE_TILE:
1454 if (drag_current_tile != drag_start_tile_shape.position) {
1455 undo_redo->create_action(TTR("Move a tile"));
1456 undo_redo->add_do_method(tile_set_atlas_source, "move_tile_in_atlas", drag_start_tile_shape.position, drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile));
1457 undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array());
1458 undo_redo->add_undo_method(tile_set_atlas_source, "move_tile_in_atlas", drag_current_tile, drag_start_tile_shape.position, drag_start_tile_shape.size);
1459 Array array;
1460 array.push_back(drag_start_tile_shape.position);
1461 array.push_back(0);
1462 undo_redo->add_undo_method(this, "_set_selection_from_array", array);
1463 undo_redo->commit_action(false);
1464 }
1465 break;
1466 case DRAG_TYPE_RECT_SELECT: {
1467 Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos, true);
1468 Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position(), true);
1469 ERR_FAIL_COND(start_base_tiles_coords == TileSetSource::INVALID_ATLAS_COORDS);
1470 ERR_FAIL_COND(new_base_tiles_coords == TileSetSource::INVALID_ATLAS_COORDS);
1471
1472 Rect2i region = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
1473 region.size += Vector2i(1, 1);
1474
1475 undo_redo->create_action(TTR("Select tiles"));
1476 undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array());
1477
1478 // Determine if we clear, then add or remove to the selection.
1479 bool add_to_selection = true;
1480 if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
1481 Vector2i coords = tile_set_atlas_source->get_tile_at_coords(start_base_tiles_coords);
1482 if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
1483 if (selection.has({ coords, 0 })) {
1484 add_to_selection = false;
1485 }
1486 }
1487 } else {
1488 selection.clear();
1489 }
1490
1491 // Modify the selection.
1492 for (int x = region.position.x; x < region.get_end().x; x++) {
1493 for (int y = region.position.y; y < region.get_end().y; y++) {
1494 Vector2i coords = Vector2i(x, y);
1495 coords = tile_set_atlas_source->get_tile_at_coords(coords);
1496 if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
1497 if (add_to_selection && !selection.has({ coords, 0 })) {
1498 selection.insert({ coords, 0 });
1499 } else if (!add_to_selection && selection.has({ coords, 0 })) {
1500 selection.erase({ coords, 0 });
1501 }
1502 }
1503 }
1504 }
1505 _update_tile_inspector();
1506 _update_tile_id_label();
1507 _update_current_tile_data_editor();
1508 undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array());
1509 undo_redo->commit_action(false);
1510 } break;
1511 case DRAG_TYPE_MAY_POPUP_MENU: {
1512 Vector2 mouse_local_pos = tile_atlas_control->get_local_mouse_position();
1513 TileSelection selected = { tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos), 0 };
1514 if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) {
1515 selected.tile = tile_set_atlas_source->get_tile_at_coords(selected.tile);
1516 }
1517
1518 // Set the selection if needed.
1519 if (selection.size() <= 1) {
1520 if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) {
1521 undo_redo->create_action(TTR("Select tiles"));
1522 undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array());
1523 selection.clear();
1524 selection.insert(selected);
1525 undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array());
1526 undo_redo->commit_action(false);
1527 _update_tile_inspector();
1528 _update_tile_id_label();
1529 _update_current_tile_data_editor();
1530 }
1531 }
1532
1533 // Pops up the correct menu, depending on whether we have a tile or not.
1534 if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selection.has(selected)) {
1535 // We have a tile.
1536 menu_option_coords = selected.tile;
1537 menu_option_alternative = 0;
1538 base_tile_popup_menu->popup(Rect2i(get_screen_transform().xform(get_local_mouse_position()), Size2i()));
1539 } else if (hovered_base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) {
1540 // We don't have a tile, but can create one.
1541 menu_option_coords = hovered_base_tile_coords;
1542 menu_option_alternative = TileSetSource::INVALID_TILE_ALTERNATIVE;
1543 empty_base_tile_popup_menu->popup(Rect2i(get_screen_transform().xform(get_local_mouse_position()), Size2i()));
1544 }
1545 } break;
1546 case DRAG_TYPE_RESIZE_TOP_LEFT:
1547 case DRAG_TYPE_RESIZE_TOP:
1548 case DRAG_TYPE_RESIZE_TOP_RIGHT:
1549 case DRAG_TYPE_RESIZE_RIGHT:
1550 case DRAG_TYPE_RESIZE_BOTTOM_RIGHT:
1551 case DRAG_TYPE_RESIZE_BOTTOM:
1552 case DRAG_TYPE_RESIZE_BOTTOM_LEFT:
1553 case DRAG_TYPE_RESIZE_LEFT:
1554 if (drag_start_tile_shape != Rect2i(drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile))) {
1555 undo_redo->create_action(TTR("Resize a tile"));
1556 undo_redo->add_do_method(tile_set_atlas_source, "move_tile_in_atlas", drag_start_tile_shape.position, drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile));
1557 undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array());
1558 undo_redo->add_undo_method(tile_set_atlas_source, "move_tile_in_atlas", drag_current_tile, drag_start_tile_shape.position, drag_start_tile_shape.size);
1559 Array array;
1560 array.push_back(drag_start_tile_shape.position);
1561 array.push_back(0);
1562 undo_redo->add_undo_method(this, "_set_selection_from_array", array);
1563 undo_redo->commit_action(false);
1564 }
1565 break;
1566 default:
1567 break;
1568 }
1569
1570 drag_modified_tiles.clear();
1571 drag_type = DRAG_TYPE_NONE;
1572 // Change mouse accordingly.
1573}
1574
1575HashMap<Vector2i, List<const PropertyInfo *>> TileSetAtlasSourceEditor::_group_properties_per_tiles(const List<PropertyInfo> &r_list, const TileSetAtlasSource *p_atlas) {
1576 // Group properties per tile.
1577 HashMap<Vector2i, List<const PropertyInfo *>> per_tile;
1578 for (const List<PropertyInfo>::Element *E_property = r_list.front(); E_property; E_property = E_property->next()) {
1579 Vector<String> components = String(E_property->get().name).split("/", true, 1);
1580 if (components.size() >= 1) {
1581 Vector<String> coord_arr = components[0].split(":");
1582 if (coord_arr.size() == 2 && coord_arr[0].is_valid_int() && coord_arr[1].is_valid_int()) {
1583 Vector2i coords = Vector2i(coord_arr[0].to_int(), coord_arr[1].to_int());
1584 per_tile[coords].push_back(&(E_property->get()));
1585 }
1586 }
1587 }
1588 return per_tile;
1589}
1590
1591void TileSetAtlasSourceEditor::_menu_option(int p_option) {
1592 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1593
1594 switch (p_option) {
1595 case TILE_DELETE: {
1596 List<PropertyInfo> list;
1597 tile_set_atlas_source->get_property_list(&list);
1598 HashMap<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source);
1599 undo_redo->create_action(TTR("Remove tile"));
1600
1601 // Remove tiles
1602 RBSet<Vector2i> removed;
1603 for (const TileSelection &E : selection) {
1604 TileSelection selected = E;
1605 if (selected.alternative == 0) {
1606 // Remove a tile.
1607 undo_redo->add_do_method(tile_set_atlas_source, "remove_tile", selected.tile);
1608 undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", selected.tile);
1609 removed.insert(selected.tile);
1610 if (per_tile.has(selected.tile)) {
1611 for (List<const PropertyInfo *>::Element *E_property = per_tile[selected.tile].front(); E_property; E_property = E_property->next()) {
1612 String property = E_property->get()->name;
1613 Variant value = tile_set_atlas_source->get(property);
1614 if (value.get_type() != Variant::NIL) {
1615 undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value);
1616 }
1617 }
1618 }
1619 }
1620 }
1621
1622 // Remove alternatives
1623 for (const TileSelection &E : selection) {
1624 TileSelection selected = E;
1625 if (selected.alternative > 0 && !removed.has(selected.tile)) {
1626 // Remove an alternative tile.
1627 undo_redo->add_do_method(tile_set_atlas_source, "remove_alternative_tile", selected.tile, selected.alternative);
1628 undo_redo->add_undo_method(tile_set_atlas_source, "create_alternative_tile", selected.tile, selected.alternative);
1629 if (per_tile.has(selected.tile)) {
1630 for (List<const PropertyInfo *>::Element *E_property = per_tile[selected.tile].front(); E_property; E_property = E_property->next()) {
1631 Vector<String> components = E_property->get()->name.split("/", true, 2);
1632 if (components.size() >= 2 && components[1].is_valid_int() && components[1].to_int() == selected.alternative) {
1633 String property = E_property->get()->name;
1634 Variant value = tile_set_atlas_source->get(property);
1635 if (value.get_type() != Variant::NIL) {
1636 undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value);
1637 }
1638 }
1639 }
1640 }
1641 }
1642 }
1643 undo_redo->commit_action();
1644 _update_fix_selected_and_hovered_tiles();
1645 _update_tile_id_label();
1646 } break;
1647 case TILE_CREATE: {
1648 undo_redo->create_action(TTR("Create a tile"));
1649 undo_redo->add_do_method(tile_set_atlas_source, "create_tile", menu_option_coords);
1650 Array array;
1651 array.push_back(menu_option_coords);
1652 array.push_back(0);
1653 undo_redo->add_do_method(this, "_set_selection_from_array", array);
1654 undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", menu_option_coords);
1655 undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array());
1656 undo_redo->commit_action();
1657 _update_tile_id_label();
1658 } break;
1659 case TILE_CREATE_ALTERNATIVE: {
1660 undo_redo->create_action(TTR("Create tile alternatives"));
1661 Array array;
1662 for (const TileSelection &E : selection) {
1663 if (E.alternative == 0) {
1664 int next_id = tile_set_atlas_source->get_next_alternative_tile_id(E.tile);
1665 undo_redo->add_do_method(tile_set_atlas_source, "create_alternative_tile", E.tile, next_id);
1666 array.push_back(E.tile);
1667 array.push_back(next_id);
1668 undo_redo->add_undo_method(tile_set_atlas_source, "remove_alternative_tile", E.tile, next_id);
1669 }
1670 }
1671 undo_redo->add_do_method(this, "_set_selection_from_array", array);
1672 undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array());
1673 undo_redo->commit_action();
1674 _update_tile_id_label();
1675 } break;
1676 case ADVANCED_AUTO_CREATE_TILES: {
1677 atlases_to_auto_create_tiles.clear();
1678 atlases_to_auto_create_tiles.append(tile_set_atlas_source);
1679 _auto_create_tiles();
1680 } break;
1681 case ADVANCED_AUTO_REMOVE_TILES: {
1682 _auto_remove_tiles();
1683 } break;
1684 case ADVANCED_CLEANUP_TILES: {
1685 _cleanup_outside_tiles();
1686 } break;
1687 }
1688}
1689
1690void TileSetAtlasSourceEditor::shortcut_input(const Ref<InputEvent> &p_event) {
1691 // Check for shortcuts.
1692 if (ED_IS_SHORTCUT("tiles_editor/delete_tile", p_event)) {
1693 if (tools_button_group->get_pressed_button() == tool_select_button && !selection.is_empty()) {
1694 _menu_option(TILE_DELETE);
1695 accept_event();
1696 }
1697 }
1698}
1699
1700void TileSetAtlasSourceEditor::_set_selection_from_array(Array p_selection) {
1701 ERR_FAIL_COND((p_selection.size() % 2) != 0);
1702 selection.clear();
1703 for (int i = 0; i < p_selection.size() / 2; i++) {
1704 TileSelection selected = { p_selection[i * 2], p_selection[i * 2 + 1] };
1705 if (tile_set_atlas_source->has_tile(selected.tile) && tile_set_atlas_source->has_alternative_tile(selected.tile, selected.alternative)) {
1706 selection.insert(selected);
1707 }
1708 }
1709 _update_tile_inspector();
1710 _update_tile_id_label();
1711 _update_atlas_view();
1712 _update_current_tile_data_editor();
1713}
1714
1715Array TileSetAtlasSourceEditor::_get_selection_as_array() {
1716 Array output;
1717 for (const TileSelection &E : selection) {
1718 output.push_back(E.tile);
1719 output.push_back(E.alternative);
1720 }
1721 return output;
1722}
1723
1724void TileSetAtlasSourceEditor::_tile_atlas_control_draw() {
1725 // Draw the selected tile.
1726 if (tools_button_group->get_pressed_button() == tool_select_button) {
1727 for (const TileSelection &E : selection) {
1728 TileSelection selected = E;
1729 if (selected.alternative == 0) {
1730 // Draw the rect.
1731 for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(selected.tile); frame++) {
1732 Color color = Color(0.0, 1.0, 0.0, frame == 0 ? 1.0 : 0.3);
1733 Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile, frame);
1734 TilesEditorUtils::draw_selection_rect(tile_atlas_control, region, color);
1735 }
1736 }
1737 }
1738
1739 if (selection.size() == 1) {
1740 // Draw the resize handles (only when it's possible to expand).
1741 TileSelection selected = selection.front()->get();
1742 if (selected.alternative == 0) {
1743 Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile);
1744 Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom();
1745 Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile);
1746 Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0);
1747 const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) };
1748 const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) };
1749 bool can_grow[4];
1750 for (int i = 0; i < 4; i++) {
1751 can_grow[i] = tile_set_atlas_source->has_room_for_tile(selected.tile + directions[i], tile_set_atlas_source->get_tile_size_in_atlas(selected.tile), tile_set_atlas_source->get_tile_animation_columns(selected.tile), tile_set_atlas_source->get_tile_animation_separation(selected.tile), tile_set_atlas_source->get_tile_animation_frames_count(selected.tile), selected.tile);
1752 can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1;
1753 }
1754 for (int i = 0; i < 4; i++) {
1755 Vector2 pos = rect.position + rect.size * coords[i];
1756 if (can_grow[i] && can_grow[(i + 3) % 4]) {
1757 tile_atlas_control->draw_texture_rect(resize_handle, Rect2(pos, zoomed_size), false);
1758 } else {
1759 tile_atlas_control->draw_texture_rect(resize_handle_disabled, Rect2(pos, zoomed_size), false);
1760 }
1761 Vector2 next_pos = rect.position + rect.size * coords[(i + 1) % 4];
1762 if (can_grow[i]) {
1763 tile_atlas_control->draw_texture_rect(resize_handle, Rect2((pos + next_pos) / 2.0, zoomed_size), false);
1764 } else {
1765 tile_atlas_control->draw_texture_rect(resize_handle_disabled, Rect2((pos + next_pos) / 2.0, zoomed_size), false);
1766 }
1767 }
1768 }
1769 }
1770 }
1771
1772 if (drag_type == DRAG_TYPE_REMOVE_TILES) {
1773 // Draw the tiles to be removed.
1774 for (const Vector2i &E : drag_modified_tiles) {
1775 for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(E); frame++) {
1776 TilesEditorUtils::draw_selection_rect(tile_atlas_control, tile_set_atlas_source->get_tile_texture_region(E, frame), Color(0.0, 0.0, 0.0));
1777 }
1778 }
1779 } else if (drag_type == DRAG_TYPE_RECT_SELECT || drag_type == DRAG_TYPE_REMOVE_TILES_USING_RECT) {
1780 // Draw tiles to be removed.
1781 Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos, true);
1782 Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position(), true);
1783 Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
1784 area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size()));
1785
1786 Color color = Color(0.0, 0.0, 0.0);
1787 if (drag_type == DRAG_TYPE_RECT_SELECT) {
1788 color = Color(1.0, 1.0, 0.0);
1789 }
1790
1791 RBSet<Vector2i> to_paint;
1792 for (int x = area.get_position().x; x < area.get_end().x; x++) {
1793 for (int y = area.get_position().y; y < area.get_end().y; y++) {
1794 Vector2i coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y));
1795 if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
1796 to_paint.insert(coords);
1797 }
1798 }
1799 }
1800
1801 for (const Vector2i &E : to_paint) {
1802 Vector2i coords = E;
1803 TilesEditorUtils::draw_selection_rect(tile_atlas_control, tile_set_atlas_source->get_tile_texture_region(coords), color);
1804 }
1805 } else if (drag_type == DRAG_TYPE_CREATE_TILES_USING_RECT) {
1806 // Draw tiles to be created.
1807 Vector2i margins = tile_set_atlas_source->get_margins();
1808 Vector2i separation = tile_set_atlas_source->get_separation();
1809 Vector2i tile_size = tile_set_atlas_source->get_texture_region_size();
1810
1811 Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos, true);
1812 Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position(), true);
1813 Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
1814 area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size()));
1815 for (int x = area.get_position().x; x < area.get_end().x; x++) {
1816 for (int y = area.get_position().y; y < area.get_end().y; y++) {
1817 Vector2i coords = Vector2i(x, y);
1818 if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
1819 Vector2i origin = margins + (coords * (tile_size + separation));
1820 TilesEditorUtils::draw_selection_rect(tile_atlas_control, Rect2i(origin, tile_size));
1821 }
1822 }
1823 }
1824 }
1825
1826 // Draw the hovered tile.
1827 if (drag_type == DRAG_TYPE_REMOVE_TILES_USING_RECT || drag_type == DRAG_TYPE_CREATE_TILES_USING_RECT) {
1828 // Draw the rect.
1829 Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos, true);
1830 Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position(), true);
1831 Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
1832 area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size()));
1833 Vector2i margins = tile_set_atlas_source->get_margins();
1834 Vector2i separation = tile_set_atlas_source->get_separation();
1835 Vector2i tile_size = tile_set_atlas_source->get_texture_region_size();
1836 Vector2i origin = margins + (area.position * (tile_size + separation));
1837 TilesEditorUtils::draw_selection_rect(tile_atlas_control, Rect2i(origin, area.size * tile_size));
1838 } else {
1839 Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
1840 if (hovered_base_tile_coords.x >= 0 && hovered_base_tile_coords.y >= 0 && hovered_base_tile_coords.x < grid_size.x && hovered_base_tile_coords.y < grid_size.y) {
1841 Vector2i hovered_tile = tile_set_atlas_source->get_tile_at_coords(hovered_base_tile_coords);
1842 if (hovered_tile != TileSetSource::INVALID_ATLAS_COORDS) {
1843 // Draw existing hovered tile.
1844 for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(hovered_tile); frame++) {
1845 Color color = Color(1.0, 0.8, 0.0, frame == 0 ? 0.6 : 0.3);
1846 TilesEditorUtils::draw_selection_rect(tile_atlas_control, tile_set_atlas_source->get_tile_texture_region(hovered_tile, frame), color);
1847 }
1848 } else {
1849 // Draw empty tile, only in add/remove tiles mode.
1850 if (tools_button_group->get_pressed_button() == tool_setup_atlas_source_button) {
1851 Vector2i margins = tile_set_atlas_source->get_margins();
1852 Vector2i separation = tile_set_atlas_source->get_separation();
1853 Vector2i tile_size = tile_set_atlas_source->get_texture_region_size();
1854 Vector2i origin = margins + (hovered_base_tile_coords * (tile_size + separation));
1855 TilesEditorUtils::draw_selection_rect(tile_atlas_control, Rect2i(origin, tile_size));
1856 }
1857 }
1858 }
1859 }
1860}
1861
1862void TileSetAtlasSourceEditor::_tile_atlas_control_unscaled_draw() {
1863 if (current_tile_data_editor) {
1864 // Draw the preview of the selected property.
1865 for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
1866 Vector2i coords = tile_set_atlas_source->get_tile_id(i);
1867 Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(coords);
1868 Vector2i position = texture_region.get_center() + tile_set_atlas_source->get_tile_data(coords, 0)->get_texture_origin();
1869
1870 Transform2D xform = tile_atlas_control->get_parent_control()->get_transform();
1871 xform.translate_local(position);
1872
1873 if (tools_button_group->get_pressed_button() == tool_select_button && selection.has({ coords, 0 })) {
1874 continue;
1875 }
1876
1877 TileMapCell cell;
1878 cell.source_id = tile_set_atlas_source_id;
1879 cell.set_atlas_coords(coords);
1880 cell.alternative_tile = 0;
1881 current_tile_data_editor->draw_over_tile(tile_atlas_control_unscaled, xform, cell);
1882 }
1883
1884 // Draw the selection on top of other.
1885 if (tools_button_group->get_pressed_button() == tool_select_button) {
1886 for (const TileSelection &E : selection) {
1887 if (E.alternative != 0) {
1888 continue;
1889 }
1890 Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(E.tile);
1891 Vector2i position = texture_region.get_center() + tile_set_atlas_source->get_tile_data(E.tile, 0)->get_texture_origin();
1892
1893 Transform2D xform = tile_atlas_control->get_parent_control()->get_transform();
1894 xform.translate_local(position);
1895
1896 TileMapCell cell;
1897 cell.source_id = tile_set_atlas_source_id;
1898 cell.set_atlas_coords(E.tile);
1899 cell.alternative_tile = 0;
1900 current_tile_data_editor->draw_over_tile(tile_atlas_control_unscaled, xform, cell, true);
1901 }
1902 }
1903
1904 // Call the TileData's editor custom draw function.
1905 if (tools_button_group->get_pressed_button() == tool_paint_button) {
1906 Transform2D xform = tile_atlas_control->get_parent_control()->get_transform();
1907 current_tile_data_editor->forward_draw_over_atlas(tile_atlas_view, tile_set_atlas_source, tile_atlas_control_unscaled, xform);
1908 }
1909 }
1910}
1911
1912void TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event) {
1913 // Update the hovered alternative tile.
1914 hovered_alternative_tile_coords = tile_atlas_view->get_alternative_tile_at_pos(alternative_tiles_control->get_local_mouse_position());
1915
1916 // Forward the event to the current tile data editor if we are in the painting mode.
1917 if (tools_button_group->get_pressed_button() == tool_paint_button) {
1918 if (current_tile_data_editor) {
1919 current_tile_data_editor->forward_painting_alternatives_gui_input(tile_atlas_view, tile_set_atlas_source, p_event);
1920 }
1921 tile_atlas_control->queue_redraw();
1922 tile_atlas_control_unscaled->queue_redraw();
1923 alternative_tiles_control->queue_redraw();
1924 alternative_tiles_control_unscaled->queue_redraw();
1925 tile_atlas_view->queue_redraw();
1926 return;
1927 }
1928
1929 Ref<InputEventMouseMotion> mm = p_event;
1930 if (mm.is_valid()) {
1931 tile_atlas_control->queue_redraw();
1932 tile_atlas_control_unscaled->queue_redraw();
1933 alternative_tiles_control->queue_redraw();
1934 alternative_tiles_control_unscaled->queue_redraw();
1935
1936 if (drag_type == DRAG_TYPE_MAY_POPUP_MENU) {
1937 if (Vector2(drag_start_mouse_pos).distance_to(alternative_tiles_control->get_local_mouse_position()) > 5.0 * EDSCALE) {
1938 drag_type = DRAG_TYPE_NONE;
1939 }
1940 }
1941 }
1942
1943 Ref<InputEventMouseButton> mb = p_event;
1944 if (mb.is_valid()) {
1945 Vector2 mouse_local_pos = alternative_tiles_control->get_local_mouse_position();
1946 if (mb->get_button_index() == MouseButton::LEFT) {
1947 if (mb->is_pressed()) {
1948 // Left click pressed.
1949 if (tools_button_group->get_pressed_button() == tool_select_button) {
1950 Vector3 tile = tile_atlas_view->get_alternative_tile_at_pos(mouse_local_pos);
1951
1952 selection.clear();
1953 TileSelection selected = { Vector2i(tile.x, tile.y), int(tile.z) };
1954 if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) {
1955 selection.insert(selected);
1956 }
1957
1958 _update_tile_inspector();
1959 _update_tile_id_label();
1960 }
1961 }
1962 } else if (mb->get_button_index() == MouseButton::RIGHT) {
1963 if (mb->is_pressed()) {
1964 drag_type = DRAG_TYPE_MAY_POPUP_MENU;
1965 drag_start_mouse_pos = alternative_tiles_control->get_local_mouse_position();
1966 } else {
1967 if (drag_type == DRAG_TYPE_MAY_POPUP_MENU) {
1968 // Right click released and wasn't dragged too far
1969 Vector3 tile = tile_atlas_view->get_alternative_tile_at_pos(mouse_local_pos);
1970
1971 selection.clear();
1972 TileSelection selected = { Vector2i(tile.x, tile.y), int(tile.z) };
1973 if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) {
1974 selection.insert(selected);
1975 }
1976
1977 _update_tile_inspector();
1978 _update_tile_id_label();
1979
1980 if (selection.size() == 1) {
1981 selected = selection.front()->get();
1982 menu_option_coords = selected.tile;
1983 menu_option_alternative = selected.alternative;
1984 alternative_tile_popup_menu->popup(Rect2i(get_screen_transform().xform(get_local_mouse_position()), Size2i()));
1985 }
1986 }
1987
1988 drag_type = DRAG_TYPE_NONE;
1989 }
1990 }
1991 tile_atlas_control->queue_redraw();
1992 tile_atlas_control_unscaled->queue_redraw();
1993 alternative_tiles_control->queue_redraw();
1994 alternative_tiles_control_unscaled->queue_redraw();
1995 }
1996}
1997
1998void TileSetAtlasSourceEditor::_tile_alternatives_control_mouse_exited() {
1999 hovered_alternative_tile_coords = Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE);
2000 tile_atlas_control->queue_redraw();
2001 tile_atlas_control_unscaled->queue_redraw();
2002 alternative_tiles_control->queue_redraw();
2003 alternative_tiles_control_unscaled->queue_redraw();
2004}
2005
2006void TileSetAtlasSourceEditor::_tile_alternatives_control_draw() {
2007 // Update the hovered alternative tile.
2008 if (tools_button_group->get_pressed_button() == tool_select_button) {
2009 // Draw hovered tile.
2010 Vector2i coords = Vector2(hovered_alternative_tile_coords.x, hovered_alternative_tile_coords.y);
2011 if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
2012 Rect2i rect = tile_atlas_view->get_alternative_tile_rect(coords, hovered_alternative_tile_coords.z);
2013 if (rect != Rect2i()) {
2014 TilesEditorUtils::draw_selection_rect(alternative_tiles_control, rect, Color(1.0, 0.8, 0.0, 0.5));
2015 }
2016 }
2017
2018 // Draw selected tile.
2019 for (const TileSelection &E : selection) {
2020 TileSelection selected = E;
2021 if (selected.alternative >= 1) {
2022 Rect2i rect = tile_atlas_view->get_alternative_tile_rect(selected.tile, selected.alternative);
2023 if (rect != Rect2i()) {
2024 TilesEditorUtils::draw_selection_rect(alternative_tiles_control, rect);
2025 }
2026 }
2027 }
2028 }
2029}
2030
2031void TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw() {
2032 // Draw the preview of the selected property.
2033 if (current_tile_data_editor) {
2034 // Draw the preview of the currently selected property.
2035 for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
2036 Vector2i coords = tile_set_atlas_source->get_tile_id(i);
2037 for (int j = 0; j < tile_set_atlas_source->get_alternative_tiles_count(coords); j++) {
2038 int alternative_tile = tile_set_atlas_source->get_alternative_tile_id(coords, j);
2039 if (alternative_tile == 0) {
2040 continue;
2041 }
2042 Rect2i rect = tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile);
2043 Vector2 position = rect.get_center() + tile_set_atlas_source->get_tile_data(coords, alternative_tile)->get_texture_origin();
2044
2045 Transform2D xform = alternative_tiles_control->get_parent_control()->get_transform();
2046 xform.translate_local(position);
2047
2048 if (tools_button_group->get_pressed_button() == tool_select_button && selection.has({ coords, alternative_tile })) {
2049 continue;
2050 }
2051
2052 TileMapCell cell;
2053 cell.source_id = tile_set_atlas_source_id;
2054 cell.set_atlas_coords(coords);
2055 cell.alternative_tile = alternative_tile;
2056 current_tile_data_editor->draw_over_tile(alternative_tiles_control_unscaled, xform, cell);
2057 }
2058 }
2059
2060 // Draw the selection on top of other.
2061 if (tools_button_group->get_pressed_button() == tool_select_button) {
2062 for (const TileSelection &E : selection) {
2063 if (E.alternative == 0) {
2064 continue;
2065 }
2066 Rect2i rect = tile_atlas_view->get_alternative_tile_rect(E.tile, E.alternative);
2067 Vector2 position = rect.get_center() + tile_set_atlas_source->get_tile_data(E.tile, E.alternative)->get_texture_origin();
2068
2069 Transform2D xform = alternative_tiles_control->get_parent_control()->get_transform();
2070 xform.translate_local(position);
2071
2072 TileMapCell cell;
2073 cell.source_id = tile_set_atlas_source_id;
2074 cell.set_atlas_coords(E.tile);
2075 cell.alternative_tile = E.alternative;
2076 current_tile_data_editor->draw_over_tile(alternative_tiles_control_unscaled, xform, cell, true);
2077 }
2078 }
2079
2080 // Call the TileData's editor custom draw function.
2081 if (tools_button_group->get_pressed_button() == tool_paint_button) {
2082 Transform2D xform = tile_atlas_control->get_parent_control()->get_transform();
2083 current_tile_data_editor->forward_draw_over_alternatives(tile_atlas_view, tile_set_atlas_source, alternative_tiles_control_unscaled, xform);
2084 }
2085 }
2086}
2087
2088void TileSetAtlasSourceEditor::_tile_set_changed() {
2089 if (tile_set->get_source_count() == 0) {
2090 // No sources, so nothing to do here anymore.
2091 tile_set->disconnect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed));
2092 tile_set = Ref<TileSet>();
2093 return;
2094 }
2095
2096 tile_set_changed_needs_update = true;
2097}
2098
2099void TileSetAtlasSourceEditor::_tile_proxy_object_changed(String p_what) {
2100 tile_set_changed_needs_update = false; // Avoid updating too many things.
2101 _update_atlas_view();
2102}
2103
2104void TileSetAtlasSourceEditor::_atlas_source_proxy_object_changed(String p_what) {
2105 if (p_what == "texture" && !atlas_source_proxy_object->get("texture").is_null()) {
2106 atlases_to_auto_create_tiles.clear();
2107 atlases_to_auto_create_tiles.append(tile_set_atlas_source);
2108 confirm_auto_create_tiles->popup_centered();
2109 } else if (p_what == "id") {
2110 emit_signal(SNAME("source_id_changed"), atlas_source_proxy_object->get_id());
2111 }
2112}
2113
2114void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) {
2115 EditorUndoRedoManager *undo_redo_man = Object::cast_to<EditorUndoRedoManager>(p_undo_redo);
2116 ERR_FAIL_NULL(undo_redo_man);
2117
2118#define ADD_UNDO(obj, property) undo_redo_man->add_undo_property(obj, property, obj->get(property));
2119
2120 AtlasTileProxyObject *tile_data_proxy = Object::cast_to<AtlasTileProxyObject>(p_edited);
2121 if (tile_data_proxy) {
2122 UndoRedo *internal_undo_redo = undo_redo_man->get_history_for_object(tile_data_proxy).undo_redo;
2123 internal_undo_redo->start_force_keep_in_merge_ends();
2124
2125 Vector<String> components = String(p_property).split("/", true, 2);
2126 if (components.size() == 2 && components[1] == "polygons_count") {
2127 int layer_index = components[0].trim_prefix("physics_layer_").to_int();
2128 int new_polygons_count = p_new_value;
2129 int old_polygons_count = tile_data_proxy->get(vformat("physics_layer_%d/polygons_count", layer_index));
2130 if (new_polygons_count < old_polygons_count) {
2131 for (int i = new_polygons_count; i < old_polygons_count; i++) {
2132 ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/points", layer_index, i));
2133 ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, i));
2134 ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, i));
2135 }
2136 }
2137 } else if (p_property == "terrain_set") {
2138 int current_terrain_set = tile_data_proxy->get("terrain_set");
2139 ADD_UNDO(tile_data_proxy, "terrain");
2140 for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
2141 TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
2142 if (tile_set->is_valid_terrain_peering_bit(current_terrain_set, bit)) {
2143 ADD_UNDO(tile_data_proxy, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]));
2144 }
2145 }
2146 }
2147 internal_undo_redo->end_force_keep_in_merge_ends();
2148 }
2149#undef ADD_UNDO
2150}
2151
2152Vector2i TileSetAtlasSourceEditor::_get_drag_offset_tile_coords(const Vector2i &p_offset) const {
2153 Vector2i half_tile_size = tile_set->get_tile_size() / 2;
2154 Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position() + half_tile_size * p_offset);
2155 return new_base_tiles_coords.max(Vector2i(-1, -1)).min(tile_set_atlas_source->get_atlas_grid_size());
2156}
2157
2158void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id) {
2159 ERR_FAIL_COND(!p_tile_set.is_valid());
2160 ERR_FAIL_NULL(p_tile_set_atlas_source);
2161 ERR_FAIL_COND(p_source_id < 0);
2162 ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source);
2163
2164 bool new_read_only_state = false;
2165 if (p_tile_set.is_valid()) {
2166 new_read_only_state = EditorNode::get_singleton()->is_resource_read_only(p_tile_set);
2167 }
2168
2169 if (p_tile_set == tile_set && p_tile_set_atlas_source == tile_set_atlas_source && p_source_id == tile_set_atlas_source_id && new_read_only_state == read_only) {
2170 return;
2171 }
2172
2173 // Remove listener for old objects.
2174 if (tile_set.is_valid()) {
2175 tile_set->disconnect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed));
2176 }
2177
2178 if (tile_set_atlas_source) {
2179 tile_set_atlas_source->disconnect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_update_source_texture));
2180 if (atlas_source_texture.is_valid()) {
2181 atlas_source_texture->disconnect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_check_outside_tiles));
2182 atlas_source_texture = Ref<Texture2D>();
2183 }
2184 }
2185
2186 // Clear the selection.
2187 selection.clear();
2188
2189 // Change the edited object.
2190 tile_set = p_tile_set;
2191 tile_set_atlas_source = p_tile_set_atlas_source;
2192 tile_set_atlas_source_id = p_source_id;
2193
2194 // Read-only is off by default.
2195 read_only = new_read_only_state;
2196
2197 if (tile_set.is_valid()) {
2198 tile_set->connect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed));
2199 }
2200
2201 if (tile_set_atlas_source) {
2202 tile_set_atlas_source->connect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_update_source_texture));
2203 _update_source_texture();
2204 }
2205
2206 if (read_only && tools_button_group->get_pressed_button() == tool_paint_button) {
2207 tool_paint_button->set_pressed(false);
2208 tool_setup_atlas_source_button->set_pressed(true);
2209 }
2210
2211 // Disable buttons in read-only mode.
2212 tool_paint_button->set_disabled(read_only);
2213 tools_settings_erase_button->set_disabled(read_only);
2214 tool_advanced_menu_button->set_disabled(read_only);
2215
2216 // Update everything.
2217 _update_source_inspector();
2218
2219 // Update the selected tile.
2220 _update_fix_selected_and_hovered_tiles();
2221 _update_tile_id_label();
2222 _update_atlas_view();
2223 _update_atlas_source_inspector();
2224 _update_tile_inspector();
2225 _update_tile_data_editors();
2226 _update_current_tile_data_editor();
2227}
2228
2229void TileSetAtlasSourceEditor::init_new_atlases(const Vector<Ref<TileSetAtlasSource>> &p_atlases) {
2230 tool_setup_atlas_source_button->set_pressed(true);
2231 atlases_to_auto_create_tiles = p_atlases;
2232 confirm_auto_create_tiles->popup_centered();
2233}
2234
2235void TileSetAtlasSourceEditor::_update_source_texture() {
2236 if (tile_set_atlas_source && tile_set_atlas_source->get_texture() == atlas_source_texture) {
2237 return;
2238 }
2239
2240 if (atlas_source_texture.is_valid()) {
2241 atlas_source_texture->disconnect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_check_outside_tiles));
2242 atlas_source_texture = Ref<Texture2D>();
2243 }
2244
2245 if (!tile_set_atlas_source || tile_set_atlas_source->get_texture().is_null()) {
2246 return;
2247 }
2248 atlas_source_texture = tile_set_atlas_source->get_texture();
2249 atlas_source_texture->connect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_check_outside_tiles), CONNECT_DEFERRED);
2250 _check_outside_tiles();
2251}
2252
2253void TileSetAtlasSourceEditor::_check_outside_tiles() {
2254 ERR_FAIL_NULL(tile_set_atlas_source);
2255 outside_tiles_warning->set_visible(!read_only && tile_set_atlas_source->has_tiles_outside_texture());
2256 tool_advanced_menu_button->get_popup()->set_item_disabled(tool_advanced_menu_button->get_popup()->get_item_index(ADVANCED_CLEANUP_TILES), !tile_set_atlas_source->has_tiles_outside_texture());
2257}
2258
2259void TileSetAtlasSourceEditor::_cleanup_outside_tiles() {
2260 ERR_FAIL_NULL(tile_set_atlas_source);
2261
2262 List<PropertyInfo> list;
2263 tile_set_atlas_source->get_property_list(&list);
2264 HashMap<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source);
2265 Vector<Vector2i> tiles_outside = tile_set_atlas_source->get_tiles_outside_texture();
2266
2267 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
2268 undo_redo->create_action(TTR("Remove Tiles Outside the Texture"));
2269
2270 undo_redo->add_do_method(tile_set_atlas_source, "clear_tiles_outside_texture");
2271 for (const Vector2i &coords : tiles_outside) {
2272 undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", coords);
2273 if (per_tile.has(coords)) {
2274 for (List<const PropertyInfo *>::Element *E_property = per_tile[coords].front(); E_property; E_property = E_property->next()) {
2275 String property = E_property->get()->name;
2276 Variant value = tile_set_atlas_source->get(property);
2277 if (value.get_type() != Variant::NIL) {
2278 undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value);
2279 }
2280 }
2281 }
2282 }
2283
2284 undo_redo->add_do_method(this, "_check_outside_tiles");
2285 undo_redo->add_undo_method(this, "_check_outside_tiles");
2286 undo_redo->commit_action();
2287 outside_tiles_warning->hide();
2288}
2289
2290void TileSetAtlasSourceEditor::_auto_create_tiles() {
2291 for (Ref<TileSetAtlasSource> &atlas_source : atlases_to_auto_create_tiles) {
2292 if (atlas_source.is_valid()) {
2293 Ref<Texture2D> texture = atlas_source->get_texture();
2294 if (texture.is_valid()) {
2295 Vector2i margins = atlas_source->get_margins();
2296 Vector2i separation = atlas_source->get_separation();
2297 Vector2i texture_region_size = atlas_source->get_texture_region_size();
2298 Size2i grid_size = atlas_source->get_atlas_grid_size();
2299 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
2300 undo_redo->create_action(TTR("Create tiles in non-transparent texture regions"));
2301 for (int y = 0; y < grid_size.y; y++) {
2302 for (int x = 0; x < grid_size.x; x++) {
2303 // Check if we have a tile at the coord.
2304 Vector2i coords = Vector2i(x, y);
2305 if (atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
2306 // Check if the texture is empty at the given coords.
2307 Rect2i region = Rect2i(margins + (coords * (texture_region_size + separation)), texture_region_size);
2308 bool is_opaque = false;
2309 for (int region_x = region.get_position().x; region_x < region.get_end().x; region_x++) {
2310 for (int region_y = region.get_position().y; region_y < region.get_end().y; region_y++) {
2311 if (texture->is_pixel_opaque(region_x, region_y)) {
2312 is_opaque = true;
2313 break;
2314 }
2315 }
2316 if (is_opaque) {
2317 break;
2318 }
2319 }
2320
2321 // If we do have opaque pixels, create a tile.
2322 if (is_opaque) {
2323 undo_redo->add_do_method(*atlas_source, "create_tile", coords);
2324 undo_redo->add_undo_method(*atlas_source, "remove_tile", coords);
2325 }
2326 }
2327 }
2328 }
2329 undo_redo->commit_action();
2330 }
2331 }
2332 }
2333
2334 _cancel_auto_create_tiles();
2335}
2336
2337void TileSetAtlasSourceEditor::_cancel_auto_create_tiles() {
2338 atlases_to_auto_create_tiles.clear();
2339}
2340
2341void TileSetAtlasSourceEditor::_auto_remove_tiles() {
2342 if (!tile_set_atlas_source) {
2343 return;
2344 }
2345
2346 Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
2347 if (texture.is_valid()) {
2348 Vector2i margins = tile_set_atlas_source->get_margins();
2349 Vector2i separation = tile_set_atlas_source->get_separation();
2350 Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
2351 Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
2352
2353 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
2354 undo_redo->create_action(TTR("Remove tiles in fully transparent texture regions"));
2355
2356 List<PropertyInfo> list;
2357 tile_set_atlas_source->get_property_list(&list);
2358 HashMap<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source);
2359
2360 for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
2361 Vector2i coords = tile_set_atlas_source->get_tile_id(i);
2362 Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(coords);
2363
2364 // Skip tiles outside texture.
2365 if ((coords.x + size_in_atlas.x) > grid_size.x || (coords.y + size_in_atlas.y) > grid_size.y) {
2366 continue;
2367 }
2368
2369 // Check if the texture is empty at the given coords.
2370 Rect2i region = Rect2i(margins + (coords * (texture_region_size + separation)), texture_region_size * size_in_atlas);
2371 bool is_opaque = false;
2372 for (int region_x = region.get_position().x; region_x < region.get_end().x; region_x++) {
2373 for (int region_y = region.get_position().y; region_y < region.get_end().y; region_y++) {
2374 if (texture->is_pixel_opaque(region_x, region_y)) {
2375 is_opaque = true;
2376 break;
2377 }
2378 }
2379 if (is_opaque) {
2380 break;
2381 }
2382 }
2383
2384 // If we do have opaque pixels, create a tile.
2385 if (!is_opaque) {
2386 undo_redo->add_do_method(tile_set_atlas_source, "remove_tile", coords);
2387 undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", coords);
2388 if (per_tile.has(coords)) {
2389 for (List<const PropertyInfo *>::Element *E_property = per_tile[coords].front(); E_property; E_property = E_property->next()) {
2390 String property = E_property->get()->name;
2391 Variant value = tile_set_atlas_source->get(property);
2392 if (value.get_type() != Variant::NIL) {
2393 undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value);
2394 }
2395 }
2396 }
2397 }
2398 }
2399 undo_redo->commit_action();
2400 }
2401}
2402
2403void TileSetAtlasSourceEditor::_notification(int p_what) {
2404 switch (p_what) {
2405 case NOTIFICATION_ENTER_TREE:
2406 case NOTIFICATION_THEME_CHANGED: {
2407 tool_setup_atlas_source_button->set_icon(get_editor_theme_icon(SNAME("Tools")));
2408 tool_select_button->set_icon(get_editor_theme_icon(SNAME("ToolSelect")));
2409 tool_paint_button->set_icon(get_editor_theme_icon(SNAME("CanvasItem")));
2410
2411 tools_settings_erase_button->set_icon(get_editor_theme_icon(SNAME("Eraser")));
2412 tool_advanced_menu_button->set_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
2413 outside_tiles_warning->set_texture(get_editor_theme_icon(SNAME("StatusWarning")));
2414
2415 resize_handle = get_editor_theme_icon(SNAME("EditorHandle"));
2416 resize_handle_disabled = get_editor_theme_icon(SNAME("EditorHandleDisabled"));
2417 } break;
2418
2419 case NOTIFICATION_INTERNAL_PROCESS: {
2420 if (tile_set_changed_needs_update) {
2421 // Read-only is off by default
2422 read_only = false;
2423 // Add the listener again and check for read-only status.
2424 if (tile_set.is_valid()) {
2425 read_only = EditorNode::get_singleton()->is_resource_read_only(tile_set);
2426 }
2427
2428 // Disable buttons in read-only mode.
2429 tool_paint_button->set_disabled(read_only);
2430 tools_settings_erase_button->set_disabled(read_only);
2431 tool_advanced_menu_button->set_disabled(read_only);
2432
2433 // Update everything.
2434 _update_source_inspector();
2435
2436 // Update the selected tile.
2437 _update_fix_selected_and_hovered_tiles();
2438 _update_tile_id_label();
2439 _update_atlas_view();
2440 _update_atlas_source_inspector();
2441 _update_tile_inspector();
2442 _update_tile_data_editors();
2443 _update_current_tile_data_editor();
2444
2445 tile_set_changed_needs_update = false;
2446 }
2447 } break;
2448
2449 case NOTIFICATION_EXIT_TREE: {
2450 for (KeyValue<String, TileDataEditor *> &E : tile_data_editors) {
2451 Control *toolbar = E.value->get_toolbar();
2452 if (toolbar->get_parent() == tool_settings_tile_data_toolbar_container) {
2453 tool_settings_tile_data_toolbar_container->remove_child(toolbar);
2454 }
2455 }
2456 } break;
2457 }
2458}
2459
2460void TileSetAtlasSourceEditor::_bind_methods() {
2461 ClassDB::bind_method(D_METHOD("_set_selection_from_array"), &TileSetAtlasSourceEditor::_set_selection_from_array);
2462 ClassDB::bind_method(D_METHOD("_check_outside_tiles"), &TileSetAtlasSourceEditor::_check_outside_tiles);
2463
2464 ADD_SIGNAL(MethodInfo("source_id_changed", PropertyInfo(Variant::INT, "source_id")));
2465}
2466
2467TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() {
2468 set_shortcut_context(this);
2469 set_process_shortcut_input(true);
2470 set_process_internal(true);
2471 TileSetEditor::get_singleton()->register_split(this);
2472
2473 // Middle panel.
2474 VBoxContainer *middle_vbox_container = memnew(VBoxContainer);
2475 middle_vbox_container->set_custom_minimum_size(Size2(200, 0) * EDSCALE);
2476 add_child(middle_vbox_container);
2477
2478 // -- Toolbox --
2479 tools_button_group.instantiate();
2480 tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles).unbind(1));
2481 tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_id_label).unbind(1));
2482 tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_source_inspector).unbind(1));
2483 tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_inspector).unbind(1));
2484 tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_data_editors).unbind(1));
2485 tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_current_tile_data_editor).unbind(1));
2486 tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view).unbind(1));
2487 tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_toolbar).unbind(1));
2488
2489 HBoxContainer *toolbox = memnew(HBoxContainer);
2490 middle_vbox_container->add_child(toolbox);
2491
2492 tool_setup_atlas_source_button = memnew(Button);
2493 tool_setup_atlas_source_button->set_text(TTR("Setup"));
2494 tool_setup_atlas_source_button->set_flat(true);
2495 tool_setup_atlas_source_button->set_toggle_mode(true);
2496 tool_setup_atlas_source_button->set_pressed(true);
2497 tool_setup_atlas_source_button->set_button_group(tools_button_group);
2498 tool_setup_atlas_source_button->set_tooltip_text(TTR("Atlas setup. Add/Remove tiles tool (use the shift key to create big tiles, control for rectangle editing)."));
2499 toolbox->add_child(tool_setup_atlas_source_button);
2500
2501 tool_select_button = memnew(Button);
2502 tool_select_button->set_text(TTR("Select"));
2503 tool_select_button->set_flat(true);
2504 tool_select_button->set_toggle_mode(true);
2505 tool_select_button->set_pressed(false);
2506 tool_select_button->set_button_group(tools_button_group);
2507 tool_select_button->set_tooltip_text(TTR("Select tiles."));
2508 toolbox->add_child(tool_select_button);
2509
2510 tool_paint_button = memnew(Button);
2511 tool_paint_button->set_text(TTR("Paint"));
2512 tool_paint_button->set_flat(true);
2513 tool_paint_button->set_toggle_mode(true);
2514 tool_paint_button->set_button_group(tools_button_group);
2515 tool_paint_button->set_tooltip_text(TTR("Paint properties."));
2516 toolbox->add_child(tool_paint_button);
2517
2518 // Tile inspector.
2519 tile_proxy_object = memnew(AtlasTileProxyObject(this));
2520 tile_proxy_object->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_proxy_object_changed));
2521
2522 tile_inspector = memnew(EditorInspector);
2523 tile_inspector->set_v_size_flags(SIZE_EXPAND_FILL);
2524 tile_inspector->set_show_categories(true);
2525 tile_inspector->edit(tile_proxy_object);
2526 tile_inspector->set_use_folding(true);
2527 tile_inspector->connect("property_selected", callable_mp(this, &TileSetAtlasSourceEditor::_inspector_property_selected));
2528 middle_vbox_container->add_child(tile_inspector);
2529
2530 tile_inspector_no_tile_selected_label = memnew(Label);
2531 tile_inspector_no_tile_selected_label->set_v_size_flags(SIZE_EXPAND | SIZE_SHRINK_CENTER);
2532 tile_inspector_no_tile_selected_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
2533 tile_inspector_no_tile_selected_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
2534 tile_inspector_no_tile_selected_label->set_text(TTR("No tiles selected.\nSelect one or more tiles from the palette to edit its properties."));
2535 middle_vbox_container->add_child(tile_inspector_no_tile_selected_label);
2536
2537 // Property values palette.
2538 tile_data_editors_scroll = memnew(ScrollContainer);
2539 tile_data_editors_scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
2540 tile_data_editors_scroll->set_v_size_flags(SIZE_EXPAND_FILL);
2541 middle_vbox_container->add_child(tile_data_editors_scroll);
2542
2543 VBoxContainer *tile_data_editors_vbox = memnew(VBoxContainer);
2544 tile_data_editors_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
2545 tile_data_editors_scroll->add_child(tile_data_editors_vbox);
2546
2547 tile_data_editors_popup = memnew(Popup);
2548
2549 tile_data_editors_label = memnew(Label);
2550 tile_data_editors_label->set_text(TTR("Paint Properties:"));
2551 tile_data_editors_label->set_theme_type_variation("HeaderSmall");
2552 tile_data_editors_vbox->add_child(tile_data_editors_label);
2553
2554 tile_data_editor_dropdown_button = memnew(Button);
2555 tile_data_editor_dropdown_button->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_draw));
2556 tile_data_editor_dropdown_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_pressed));
2557 tile_data_editors_vbox->add_child(tile_data_editor_dropdown_button);
2558 tile_data_editor_dropdown_button->add_child(tile_data_editors_popup);
2559
2560 tile_data_editors_tree = memnew(Tree);
2561 tile_data_editors_tree->set_hide_root(true);
2562 tile_data_editors_tree->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
2563 tile_data_editors_tree->set_h_scroll_enabled(false);
2564 tile_data_editors_tree->set_v_scroll_enabled(false);
2565 tile_data_editors_tree->connect("item_selected", callable_mp(this, &TileSetAtlasSourceEditor::_tile_data_editors_tree_selected));
2566 tile_data_editors_popup->add_child(tile_data_editors_tree);
2567
2568 tile_data_painting_editor_container = memnew(VBoxContainer);
2569 tile_data_painting_editor_container->set_h_size_flags(SIZE_EXPAND_FILL);
2570 tile_data_editors_vbox->add_child(tile_data_painting_editor_container);
2571
2572 // Atlas source inspector.
2573 atlas_source_proxy_object = memnew(TileSetAtlasSourceProxyObject());
2574 atlas_source_proxy_object->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_atlas_source_proxy_object_changed));
2575
2576 atlas_source_inspector = memnew(EditorInspector);
2577 atlas_source_inspector->set_v_size_flags(SIZE_EXPAND_FILL);
2578 atlas_source_inspector->set_show_categories(true);
2579 atlas_source_inspector->add_inspector_plugin(memnew(TileSourceInspectorPlugin));
2580 atlas_source_inspector->edit(atlas_source_proxy_object);
2581 middle_vbox_container->add_child(atlas_source_inspector);
2582
2583 // -- Right side --
2584 VBoxContainer *right_vbox_container = memnew(VBoxContainer);
2585 add_child(right_vbox_container);
2586
2587 // Tool settings.
2588 tool_settings = memnew(HBoxContainer);
2589 right_vbox_container->add_child(tool_settings);
2590
2591 tool_settings_tile_data_toolbar_container = memnew(HBoxContainer);
2592 tool_settings->add_child(tool_settings_tile_data_toolbar_container);
2593
2594 tools_settings_erase_button = memnew(Button);
2595 tools_settings_erase_button->set_flat(true);
2596 tools_settings_erase_button->set_toggle_mode(true);
2597 tools_settings_erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", TTR("Eraser"), Key::E));
2598 tools_settings_erase_button->set_shortcut_context(this);
2599 tool_settings->add_child(tools_settings_erase_button);
2600
2601 tool_advanced_menu_button = memnew(MenuButton);
2602 tool_advanced_menu_button->set_flat(true);
2603 tool_advanced_menu_button->get_popup()->add_item(TTR("Create Tiles in Non-Transparent Texture Regions"), ADVANCED_AUTO_CREATE_TILES);
2604 tool_advanced_menu_button->get_popup()->add_item(TTR("Remove Tiles in Fully Transparent Texture Regions"), ADVANCED_AUTO_REMOVE_TILES);
2605 tool_advanced_menu_button->get_popup()->add_item(TTR("Remove Tiles Outside the Texture"), ADVANCED_CLEANUP_TILES);
2606 tool_advanced_menu_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option));
2607 tool_settings->add_child(tool_advanced_menu_button);
2608
2609 outside_tiles_warning = memnew(TextureRect);
2610 outside_tiles_warning->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
2611 outside_tiles_warning->set_tooltip_text(vformat(TTR("The current atlas source has tiles outside the texture.\nYou can clear it using \"%s\" option in the 3 dots menu."), TTR("Remove Tiles Outside the Texture")));
2612 outside_tiles_warning->hide();
2613 tool_settings->add_child(outside_tiles_warning);
2614
2615 _update_toolbar();
2616
2617 // Right side of toolbar.
2618 Control *middle_space = memnew(Control);
2619 middle_space->set_h_size_flags(SIZE_EXPAND_FILL);
2620 tool_settings->add_child(middle_space);
2621
2622 tool_tile_id_label = memnew(Label);
2623 tool_tile_id_label->set_mouse_filter(Control::MOUSE_FILTER_STOP);
2624 tool_settings->add_child(tool_tile_id_label);
2625 _update_tile_id_label();
2626
2627 // Right panel.
2628 VBoxContainer *right_panel = memnew(VBoxContainer);
2629 right_panel->set_h_size_flags(SIZE_EXPAND_FILL);
2630 right_panel->set_v_size_flags(SIZE_EXPAND_FILL);
2631 right_vbox_container->add_child(right_panel);
2632
2633 // Tile atlas view.
2634 tile_atlas_view = memnew(TileAtlasView);
2635 tile_atlas_view->set_h_size_flags(SIZE_EXPAND_FILL);
2636 tile_atlas_view->set_v_size_flags(SIZE_EXPAND_FILL);
2637 tile_atlas_view->set_custom_minimum_size(Size2(200, 0) * EDSCALE);
2638 tile_atlas_view->connect("transform_changed", callable_mp(TilesEditorUtils::get_singleton(), &TilesEditorUtils::set_atlas_view_transform));
2639 tile_atlas_view->connect("transform_changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_view_transform_changed).unbind(2));
2640 right_panel->add_child(tile_atlas_view);
2641
2642 tile_create_help = memnew(HBoxContainer);
2643 tile_atlas_view->add_child(tile_create_help);
2644 tile_create_help->set_mouse_filter(MOUSE_FILTER_IGNORE);
2645 tile_create_help->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_LEFT, Control::PRESET_MODE_MINSIZE, 30 * EDSCALE);
2646 tile_create_help->add_theme_constant_override("separation", 30 * EDSCALE);
2647
2648 Label *help_label = memnew(Label(TTR("Hold Ctrl to create multiple tiles.")));
2649 tile_create_help->add_child(help_label);
2650
2651 help_label = memnew(Label(TTR("Hold Shift to create big tiles.")));
2652 tile_create_help->add_child(help_label);
2653
2654 base_tile_popup_menu = memnew(PopupMenu);
2655 base_tile_popup_menu->add_shortcut(ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), Key::KEY_DELETE), TILE_DELETE);
2656 base_tile_popup_menu->add_item(TTR("Create an Alternative Tile"), TILE_CREATE_ALTERNATIVE);
2657 base_tile_popup_menu->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option));
2658 tile_atlas_view->add_child(base_tile_popup_menu);
2659
2660 empty_base_tile_popup_menu = memnew(PopupMenu);
2661 empty_base_tile_popup_menu->add_item(TTR("Create a Tile"), TILE_CREATE);
2662 empty_base_tile_popup_menu->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option));
2663 tile_atlas_view->add_child(empty_base_tile_popup_menu);
2664
2665 tile_atlas_control = memnew(TileAtlasControl(this));
2666 tile_atlas_control->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_draw));
2667 tile_atlas_control->connect("mouse_exited", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_mouse_exited));
2668 tile_atlas_control->connect("gui_input", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_gui_input));
2669 tile_atlas_view->add_control_over_atlas_tiles(tile_atlas_control);
2670
2671 tile_atlas_control_unscaled = memnew(Control);
2672 tile_atlas_control_unscaled->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_unscaled_draw));
2673 tile_atlas_view->add_control_over_atlas_tiles(tile_atlas_control_unscaled, false);
2674 tile_atlas_control_unscaled->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
2675
2676 alternative_tile_popup_menu = memnew(PopupMenu);
2677 alternative_tile_popup_menu->add_shortcut(ED_SHORTCUT("tiles_editor/delete_tile", TTR("Delete"), Key::KEY_DELETE), TILE_DELETE);
2678 alternative_tile_popup_menu->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option));
2679 tile_atlas_view->add_child(alternative_tile_popup_menu);
2680
2681 alternative_tiles_control = memnew(Control);
2682 alternative_tiles_control->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_alternatives_control_draw));
2683 alternative_tiles_control->connect("mouse_exited", callable_mp(this, &TileSetAtlasSourceEditor::_tile_alternatives_control_mouse_exited));
2684 alternative_tiles_control->connect("gui_input", callable_mp(this, &TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input));
2685 tile_atlas_view->add_control_over_alternative_tiles(alternative_tiles_control);
2686
2687 alternative_tiles_control_unscaled = memnew(Control);
2688 alternative_tiles_control_unscaled->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw));
2689 tile_atlas_view->add_control_over_alternative_tiles(alternative_tiles_control_unscaled, false);
2690 alternative_tiles_control_unscaled->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
2691
2692 EditorNode::get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetAtlasSourceEditor::_undo_redo_inspector_callback));
2693
2694 // -- Dialogs --
2695 confirm_auto_create_tiles = memnew(AcceptDialog);
2696 confirm_auto_create_tiles->set_title(TTR("Auto Create Tiles in Non-Transparent Texture Regions?"));
2697 confirm_auto_create_tiles->set_text(TTR("The atlas's texture was modified.\nWould you like to automatically create tiles in the atlas?"));
2698 confirm_auto_create_tiles->set_ok_button_text(TTR("Yes"));
2699 confirm_auto_create_tiles->add_cancel_button()->set_text(TTR("No"));
2700 confirm_auto_create_tiles->connect("confirmed", callable_mp(this, &TileSetAtlasSourceEditor::_auto_create_tiles));
2701 confirm_auto_create_tiles->connect("canceled", callable_mp(this, &TileSetAtlasSourceEditor::_cancel_auto_create_tiles));
2702 add_child(confirm_auto_create_tiles);
2703
2704 // Inspector plugin.
2705 Ref<EditorInspectorPluginTileData> tile_data_inspector_plugin;
2706 tile_data_inspector_plugin.instantiate();
2707 EditorInspector::add_inspector_plugin(tile_data_inspector_plugin);
2708}
2709
2710TileSetAtlasSourceEditor::~TileSetAtlasSourceEditor() {
2711 memdelete(tile_proxy_object);
2712 memdelete(atlas_source_proxy_object);
2713
2714 // Remove listener for old objects, so the TileSet doesn't
2715 // try to call the destroyed TileSetAtlasSourceEditor.
2716 if (tile_set.is_valid()) {
2717 tile_set->disconnect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed));
2718 }
2719}
2720
2721////// EditorPropertyTilePolygon //////
2722
2723void EditorPropertyTilePolygon::_add_focusable_children(Node *p_node) {
2724 Control *control = Object::cast_to<Control>(p_node);
2725 if (control && control->get_focus_mode() != Control::FOCUS_NONE) {
2726 add_focusable(control);
2727 }
2728 for (int i = 0; i < p_node->get_child_count(); i++) {
2729 _add_focusable_children(p_node->get_child(i));
2730 }
2731}
2732
2733void EditorPropertyTilePolygon::_polygons_changed() {
2734 if (String(count_property).is_empty()) {
2735 if (base_type == "OccluderPolygon2D") {
2736 // Single OccluderPolygon2D.
2737 Ref<OccluderPolygon2D> occluder;
2738 if (generic_tile_polygon_editor->get_polygon_count() >= 1) {
2739 occluder.instantiate();
2740 occluder->set_polygon(generic_tile_polygon_editor->get_polygon(0));
2741 }
2742 emit_changed(get_edited_property(), occluder);
2743 } else if (base_type == "NavigationPolygon") {
2744 Ref<NavigationPolygon> navigation_polygon;
2745 if (generic_tile_polygon_editor->get_polygon_count() >= 1) {
2746 navigation_polygon.instantiate();
2747 for (int i = 0; i < generic_tile_polygon_editor->get_polygon_count(); i++) {
2748 Vector<Vector2> polygon = generic_tile_polygon_editor->get_polygon(i);
2749 navigation_polygon->add_outline(polygon);
2750 }
2751 navigation_polygon->make_polygons_from_outlines();
2752 }
2753 emit_changed(get_edited_property(), navigation_polygon);
2754 }
2755 } else {
2756 if (base_type.is_empty()) {
2757 // Multiple array of vertices.
2758 Vector<String> changed_properties;
2759 Array values;
2760 int count = generic_tile_polygon_editor->get_polygon_count();
2761 changed_properties.push_back(count_property);
2762 values.push_back(count);
2763 for (int i = 0; i < count; i++) {
2764 changed_properties.push_back(vformat(element_pattern, i));
2765 values.push_back(generic_tile_polygon_editor->get_polygon(i));
2766 }
2767 emit_signal(SNAME("multiple_properties_changed"), changed_properties, values, false);
2768 }
2769 }
2770}
2771
2772void EditorPropertyTilePolygon::update_property() {
2773 TileSetAtlasSourceEditor::AtlasTileProxyObject *atlas_tile_proxy_object = Object::cast_to<TileSetAtlasSourceEditor::AtlasTileProxyObject>(get_edited_object());
2774 ERR_FAIL_NULL(atlas_tile_proxy_object);
2775 ERR_FAIL_COND(atlas_tile_proxy_object->get_edited_tiles().is_empty());
2776
2777 Ref<TileSetAtlasSource> tile_set_atlas_source = atlas_tile_proxy_object->get_edited_tile_set_atlas_source();
2778 generic_tile_polygon_editor->set_tile_set(Ref<TileSet>(tile_set_atlas_source->get_tile_set()));
2779
2780 // Set the background
2781 Vector2i coords = atlas_tile_proxy_object->get_edited_tiles().front()->get().tile;
2782 int alternative = atlas_tile_proxy_object->get_edited_tiles().front()->get().alternative;
2783 TileData *tile_data = tile_set_atlas_source->get_tile_data(coords, alternative);
2784 generic_tile_polygon_editor->set_background(tile_set_atlas_source->get_texture(), tile_set_atlas_source->get_tile_texture_region(coords), tile_data->get_texture_origin(), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate());
2785
2786 // Reset the polygons.
2787 generic_tile_polygon_editor->clear_polygons();
2788
2789 if (String(count_property).is_empty()) {
2790 if (base_type == "OccluderPolygon2D") {
2791 // Single OccluderPolygon2D.
2792 Ref<OccluderPolygon2D> occluder = get_edited_property_value();
2793 generic_tile_polygon_editor->clear_polygons();
2794 if (occluder.is_valid()) {
2795 generic_tile_polygon_editor->add_polygon(occluder->get_polygon());
2796 }
2797 } else if (base_type == "NavigationPolygon") {
2798 // Single OccluderPolygon2D.
2799 Ref<NavigationPolygon> navigation_polygon = get_edited_property_value();
2800 generic_tile_polygon_editor->clear_polygons();
2801 if (navigation_polygon.is_valid()) {
2802 for (int i = 0; i < navigation_polygon->get_outline_count(); i++) {
2803 generic_tile_polygon_editor->add_polygon(navigation_polygon->get_outline(i));
2804 }
2805 }
2806 }
2807 } else {
2808 int count = get_edited_object()->get(count_property);
2809 if (base_type.is_empty()) {
2810 // Multiple array of vertices.
2811 generic_tile_polygon_editor->clear_polygons();
2812 for (int i = 0; i < count; i++) {
2813 generic_tile_polygon_editor->add_polygon(get_edited_object()->get(vformat(element_pattern, i)));
2814 }
2815 }
2816 }
2817}
2818
2819void EditorPropertyTilePolygon::setup_single_mode(const StringName &p_property, const String &p_base_type) {
2820 set_object_and_property(nullptr, p_property);
2821 base_type = p_base_type;
2822
2823 generic_tile_polygon_editor->set_multiple_polygon_mode(false);
2824}
2825
2826void EditorPropertyTilePolygon::setup_multiple_mode(const StringName &p_property, const StringName &p_count_property, const String &p_element_pattern, const String &p_base_type) {
2827 set_object_and_property(nullptr, p_property);
2828 count_property = p_count_property;
2829 element_pattern = p_element_pattern;
2830 base_type = p_base_type;
2831
2832 generic_tile_polygon_editor->set_multiple_polygon_mode(true);
2833}
2834
2835EditorPropertyTilePolygon::EditorPropertyTilePolygon() {
2836 // Setup the polygon editor.
2837 generic_tile_polygon_editor = memnew(GenericTilePolygonEditor);
2838 generic_tile_polygon_editor->set_use_undo_redo(false);
2839 generic_tile_polygon_editor->clear_polygons();
2840 add_child(generic_tile_polygon_editor);
2841 generic_tile_polygon_editor->connect("polygons_changed", callable_mp(this, &EditorPropertyTilePolygon::_polygons_changed));
2842
2843 // Add all focussable children of generic_tile_polygon_editor as focussable.
2844 _add_focusable_children(generic_tile_polygon_editor);
2845}
2846
2847////// EditorInspectorPluginTileData //////
2848
2849bool EditorInspectorPluginTileData::can_handle(Object *p_object) {
2850 return Object::cast_to<TileSetAtlasSourceEditor::AtlasTileProxyObject>(p_object) != nullptr;
2851}
2852
2853bool EditorInspectorPluginTileData::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) {
2854 Vector<String> components = String(p_path).split("/", true, 2);
2855 if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) {
2856 // Occlusion layers.
2857 int layer_index = components[0].trim_prefix("occlusion_layer_").to_int();
2858 ERR_FAIL_COND_V(layer_index < 0, false);
2859 if (components[1] == "polygon") {
2860 EditorPropertyTilePolygon *ep = memnew(EditorPropertyTilePolygon);
2861 ep->setup_single_mode(p_path, "OccluderPolygon2D");
2862 add_property_editor(p_path, ep);
2863 return true;
2864 }
2865 } else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) {
2866 // Physics layers.
2867 int layer_index = components[0].trim_prefix("physics_layer_").to_int();
2868 ERR_FAIL_COND_V(layer_index < 0, false);
2869 if (components[1] == "polygons_count") {
2870 EditorPropertyTilePolygon *ep = memnew(EditorPropertyTilePolygon);
2871 ep->setup_multiple_mode(vformat("physics_layer_%d/polygons", layer_index), vformat("physics_layer_%d/polygons_count", layer_index), vformat("physics_layer_%d/polygon_%%d/points", layer_index), "");
2872 Vector<String> properties;
2873 properties.push_back(p_path);
2874 int count = p_object->get(vformat("physics_layer_%d/polygons_count", layer_index));
2875 for (int i = 0; i < count; i++) {
2876 properties.push_back(vformat(vformat("physics_layer_%d/polygon_%d/points", layer_index, i)));
2877 }
2878 add_property_editor_for_multiple_properties("Polygons", properties, ep);
2879 return true;
2880 } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) {
2881 int polygon_index = components[1].trim_prefix("polygon_").to_int();
2882 ERR_FAIL_COND_V(polygon_index < 0, false);
2883 if (components[2] == "points") {
2884 return true;
2885 }
2886 }
2887 } else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_int()) {
2888 // Navigation layers.
2889 int layer_index = components[0].trim_prefix("navigation_layer_").to_int();
2890 ERR_FAIL_COND_V(layer_index < 0, false);
2891 if (components[1] == "polygon") {
2892 EditorPropertyTilePolygon *ep = memnew(EditorPropertyTilePolygon);
2893 ep->setup_single_mode(p_path, "NavigationPolygon");
2894 add_property_editor(p_path, ep);
2895 return true;
2896 }
2897 }
2898 return false;
2899}
2900
2901Control::CursorShape TileSetAtlasSourceEditor::TileAtlasControl::get_cursor_shape(const Point2 &p_pos) const {
2902 Control::CursorShape cursor_shape = get_default_cursor_shape();
2903 if (editor->drag_type == DRAG_TYPE_NONE) {
2904 if (editor->selection.size() == 1) {
2905 // Change the cursor depending on the hovered thing.
2906 TileSelection selected = editor->selection.front()->get();
2907 if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selected.alternative == 0) {
2908 Transform2D xform = editor->tile_atlas_control->get_global_transform().affine_inverse() * get_global_transform();
2909 Vector2 mouse_local_pos = xform.xform(p_pos);
2910 Vector2i size_in_atlas = editor->tile_set_atlas_source->get_tile_size_in_atlas(selected.tile);
2911 Rect2 region = editor->tile_set_atlas_source->get_tile_texture_region(selected.tile);
2912 Size2 zoomed_size = editor->resize_handle->get_size() / editor->tile_atlas_view->get_zoom();
2913 Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0);
2914 const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) };
2915 const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) };
2916 bool can_grow[4];
2917 for (int i = 0; i < 4; i++) {
2918 can_grow[i] = editor->tile_set_atlas_source->has_room_for_tile(selected.tile + directions[i], editor->tile_set_atlas_source->get_tile_size_in_atlas(selected.tile), editor->tile_set_atlas_source->get_tile_animation_columns(selected.tile), editor->tile_set_atlas_source->get_tile_animation_separation(selected.tile), editor->tile_set_atlas_source->get_tile_animation_frames_count(selected.tile), selected.tile);
2919 can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1;
2920 }
2921 for (int i = 0; i < 4; i++) {
2922 Vector2 pos = rect.position + rect.size * coords[i];
2923 if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) {
2924 cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE;
2925 }
2926 Vector2 next_pos = rect.position + rect.size * coords[(i + 1) % 4];
2927 if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) {
2928 cursor_shape = (i % 2) ? CURSOR_HSIZE : CURSOR_VSIZE;
2929 }
2930 }
2931 }
2932 }
2933 } else {
2934 switch (editor->drag_type) {
2935 case DRAG_TYPE_RESIZE_TOP_LEFT:
2936 case DRAG_TYPE_RESIZE_BOTTOM_RIGHT:
2937 cursor_shape = CURSOR_FDIAGSIZE;
2938 break;
2939 case DRAG_TYPE_RESIZE_TOP:
2940 case DRAG_TYPE_RESIZE_BOTTOM:
2941 cursor_shape = CURSOR_VSIZE;
2942 break;
2943 case DRAG_TYPE_RESIZE_TOP_RIGHT:
2944 case DRAG_TYPE_RESIZE_BOTTOM_LEFT:
2945 cursor_shape = CURSOR_BDIAGSIZE;
2946 break;
2947 case DRAG_TYPE_RESIZE_LEFT:
2948 case DRAG_TYPE_RESIZE_RIGHT:
2949 cursor_shape = CURSOR_HSIZE;
2950 break;
2951 case DRAG_TYPE_MOVE_TILE:
2952 cursor_shape = CURSOR_MOVE;
2953 break;
2954 default:
2955 break;
2956 }
2957 }
2958 return cursor_shape;
2959}
2960