1/**************************************************************************/
2/* tile_atlas_view.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_atlas_view.h"
32
33#include "core/input/input.h"
34#include "core/os/keyboard.h"
35#include "scene/2d/tile_map.h"
36#include "scene/gui/box_container.h"
37#include "scene/gui/label.h"
38#include "scene/gui/panel.h"
39#include "scene/gui/view_panner.h"
40
41#include "editor/editor_scale.h"
42#include "editor/editor_settings.h"
43
44void TileAtlasView::gui_input(const Ref<InputEvent> &p_event) {
45 if (panner->gui_input(p_event)) {
46 accept_event();
47 }
48}
49
50void TileAtlasView::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
51 panning += p_scroll_vec;
52 _update_zoom_and_panning(true);
53 emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning);
54}
55
56void TileAtlasView::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {
57 zoom_widget->set_zoom(zoom_widget->get_zoom() * p_zoom_factor);
58 _update_zoom_and_panning(true);
59 emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning);
60}
61
62Size2i TileAtlasView::_compute_base_tiles_control_size() {
63 // Update the texture.
64 Vector2i size;
65 Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
66 if (texture.is_valid()) {
67 size = texture->get_size();
68 }
69 return size;
70}
71
72Size2i TileAtlasView::_compute_alternative_tiles_control_size() {
73 Vector2i size;
74 for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
75 Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
76 int alternatives_count = tile_set_atlas_source->get_alternative_tiles_count(tile_id);
77 Vector2i line_size;
78 Size2i texture_region_size = tile_set_atlas_source->get_tile_texture_region(tile_id).size;
79 for (int j = 1; j < alternatives_count; j++) {
80 int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j);
81 bool transposed = tile_set_atlas_source->get_tile_data(tile_id, alternative_id)->get_transpose();
82 line_size.x += transposed ? texture_region_size.y : texture_region_size.x;
83 line_size.y = MAX(line_size.y, transposed ? texture_region_size.x : texture_region_size.y);
84 }
85 size.x = MAX(size.x, line_size.x);
86 size.y += line_size.y;
87 }
88
89 return size;
90}
91
92void TileAtlasView::_update_zoom_and_panning(bool p_zoom_on_mouse_pos) {
93 // Don't allow zoom to go below 1% or above 10000%
94 zoom_widget->set_zoom(CLAMP(zoom_widget->get_zoom(), 0.01f, 100.f));
95 float zoom = zoom_widget->get_zoom();
96
97 // Compute the minimum sizes.
98 Size2i base_tiles_control_size = _compute_base_tiles_control_size();
99 base_tiles_root_control->set_custom_minimum_size(Vector2(base_tiles_control_size) * zoom);
100
101 Size2i alternative_tiles_control_size = _compute_alternative_tiles_control_size();
102 alternative_tiles_root_control->set_custom_minimum_size(Vector2(alternative_tiles_control_size) * zoom);
103
104 // Set the texture for the base tiles.
105 Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
106
107 // Set the scales.
108 if (base_tiles_control_size.x > 0 && base_tiles_control_size.y > 0) {
109 base_tiles_drawing_root->set_scale(Vector2(zoom, zoom));
110 } else {
111 base_tiles_drawing_root->set_scale(Vector2(1, 1));
112 }
113 if (alternative_tiles_control_size.x > 0 && alternative_tiles_control_size.y > 0) {
114 alternative_tiles_drawing_root->set_scale(Vector2(zoom, zoom));
115 } else {
116 alternative_tiles_drawing_root->set_scale(Vector2(1, 1));
117 }
118
119 // Update the margin container's margins.
120 const char *constants[] = { "margin_left", "margin_top", "margin_right", "margin_bottom" };
121 for (int i = 0; i < 4; i++) {
122 margin_container->add_theme_constant_override(constants[i], margin_container_paddings[i] * zoom);
123 }
124
125 // Update the backgrounds.
126 background_left->set_size(base_tiles_root_control->get_custom_minimum_size());
127 background_right->set_size(alternative_tiles_root_control->get_custom_minimum_size());
128
129 // Zoom on the position.
130 if (p_zoom_on_mouse_pos) {
131 // Offset the panning relative to the center of panel.
132 Vector2 relative_mpos = get_local_mouse_position() - get_size() / 2;
133 panning = (panning - relative_mpos) * zoom / previous_zoom + relative_mpos;
134 } else {
135 // Center of panel.
136 panning = panning * zoom / previous_zoom;
137 }
138 button_center_view->set_disabled(panning.is_zero_approx());
139
140 previous_zoom = zoom;
141
142 center_container->set_begin(panning - center_container->get_minimum_size() / 2);
143 center_container->set_size(center_container->get_minimum_size());
144}
145
146void TileAtlasView::_zoom_widget_changed() {
147 _update_zoom_and_panning();
148 emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning);
149}
150
151void TileAtlasView::_center_view() {
152 panning = Vector2();
153 button_center_view->set_disabled(true);
154 _update_zoom_and_panning();
155 emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning);
156}
157
158void TileAtlasView::_base_tiles_root_control_gui_input(const Ref<InputEvent> &p_event) {
159 base_tiles_root_control->set_tooltip_text("");
160
161 Ref<InputEventMouseMotion> mm = p_event;
162 if (mm.is_valid()) {
163 Transform2D xform = base_tiles_drawing_root->get_transform().affine_inverse();
164 Vector2i coords = get_atlas_tile_coords_at_pos(xform.xform(mm->get_position()));
165 if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
166 coords = tile_set_atlas_source->get_tile_at_coords(coords);
167 if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
168 base_tiles_root_control->set_tooltip_text(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: 0"), source_id, coords));
169 }
170 }
171 }
172}
173
174void TileAtlasView::_draw_base_tiles() {
175 Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
176 if (texture.is_valid()) {
177 Vector2i margins = tile_set_atlas_source->get_margins();
178 Vector2i separation = tile_set_atlas_source->get_separation();
179 Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
180 Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
181
182 // Draw the texture where there is no tile.
183 for (int x = 0; x < grid_size.x; x++) {
184 for (int y = 0; y < grid_size.y; y++) {
185 Vector2i coords = Vector2i(x, y);
186 if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
187 Rect2i rect = Rect2i((texture_region_size + separation) * coords + margins, texture_region_size + separation);
188 rect = rect.intersection(Rect2i(Vector2(), texture->get_size()));
189 if (rect.size.x > 0 && rect.size.y > 0) {
190 base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
191 }
192 }
193 }
194 }
195
196 // Draw dark overlay after for performance reasons.
197 for (int x = 0; x < grid_size.x; x++) {
198 for (int y = 0; y < grid_size.y; y++) {
199 Vector2i coords = Vector2i(x, y);
200 if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
201 Rect2i rect = Rect2i((texture_region_size + separation) * coords + margins, texture_region_size + separation);
202 rect = rect.intersection(Rect2i(Vector2(), texture->get_size()));
203 if (rect.size.x > 0 && rect.size.y > 0) {
204 base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5));
205 }
206 }
207 }
208 }
209
210 // Draw the texture around the grid.
211 Rect2i rect;
212
213 // Top.
214 rect.position = Vector2i();
215 rect.set_end(Vector2i(texture->get_size().x, margins.y));
216 base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
217 base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5));
218
219 // Bottom
220 int bottom_border = margins.y + (grid_size.y * (texture_region_size.y + separation.y));
221 if (bottom_border < texture->get_size().y) {
222 rect.position = Vector2i(0, bottom_border);
223 rect.set_end(texture->get_size());
224 base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
225 base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5));
226 }
227
228 // Left
229 rect.position = Vector2i(0, margins.y);
230 rect.set_end(Vector2i(margins.x, margins.y + (grid_size.y * (texture_region_size.y + separation.y))));
231 base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
232 base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5));
233
234 // Right.
235 int right_border = margins.x + (grid_size.x * (texture_region_size.x + separation.x));
236 if (right_border < texture->get_size().x) {
237 rect.position = Vector2i(right_border, margins.y);
238 rect.set_end(Vector2i(texture->get_size().x, margins.y + (grid_size.y * (texture_region_size.y + separation.y))));
239 base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
240 base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5));
241 }
242
243 // Draw actual tiles, using their properties (modulation, etc...)
244 for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
245 Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i);
246
247 // Different materials need to be drawn with different CanvasItems.
248 RID ci_rid = _get_canvas_item_to_draw(tile_set_atlas_source->get_tile_data(atlas_coords, 0), base_tiles_draw, material_tiles_draw);
249
250 for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(atlas_coords); frame++) {
251 // Update the y to max value.
252 Rect2i base_frame_rect = tile_set_atlas_source->get_tile_texture_region(atlas_coords, frame);
253 Vector2 offset_pos = Rect2(base_frame_rect).get_center() + Vector2(tile_set_atlas_source->get_tile_data(atlas_coords, 0)->get_texture_origin());
254
255 // Draw the tile.
256 TileMap::draw_tile(ci_rid, offset_pos, tile_set, source_id, atlas_coords, 0, frame);
257 }
258 }
259
260 // Draw Dark overlay on separation in its own pass.
261 if (separation.x > 0 || separation.y > 0) {
262 for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
263 Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i);
264
265 for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(atlas_coords); frame++) {
266 // Update the y to max value.
267 Rect2i base_frame_rect = tile_set_atlas_source->get_tile_texture_region(atlas_coords, frame);
268
269 if (separation.x > 0) {
270 Rect2i right_sep_rect = Rect2i(base_frame_rect.get_position() + Vector2i(base_frame_rect.size.x, 0), Vector2i(separation.x, base_frame_rect.size.y));
271 right_sep_rect = right_sep_rect.intersection(Rect2i(Vector2(), texture->get_size()));
272 if (right_sep_rect.size.x > 0 && right_sep_rect.size.y > 0) {
273 //base_tiles_draw->draw_texture_rect_region(texture, right_sep_rect, right_sep_rect);
274 base_tiles_draw->draw_rect(right_sep_rect, Color(0.0, 0.0, 0.0, 0.5));
275 }
276 }
277
278 if (separation.y > 0) {
279 Rect2i bottom_sep_rect = Rect2i(base_frame_rect.get_position() + Vector2i(0, base_frame_rect.size.y), Vector2i(base_frame_rect.size.x + separation.x, separation.y));
280 bottom_sep_rect = bottom_sep_rect.intersection(Rect2i(Vector2(), texture->get_size()));
281 if (bottom_sep_rect.size.x > 0 && bottom_sep_rect.size.y > 0) {
282 //base_tiles_draw->draw_texture_rect_region(texture, bottom_sep_rect, bottom_sep_rect);
283 base_tiles_draw->draw_rect(bottom_sep_rect, Color(0.0, 0.0, 0.0, 0.5));
284 }
285 }
286 }
287 }
288 }
289 }
290}
291
292RID TileAtlasView::_get_canvas_item_to_draw(const TileData *p_for_data, const CanvasItem *p_base_item, HashMap<Ref<Material>, RID> &p_material_map) {
293 Ref<Material> mat = p_for_data->get_material();
294 if (mat.is_null()) {
295 return p_base_item->get_canvas_item();
296 } else if (p_material_map.has(mat)) {
297 return p_material_map[mat];
298 } else {
299 RID ci_rid = RS::get_singleton()->canvas_item_create();
300 RS::get_singleton()->canvas_item_set_parent(ci_rid, p_base_item->get_canvas_item());
301 RS::get_singleton()->canvas_item_set_material(ci_rid, mat->get_rid());
302 p_material_map[mat] = ci_rid;
303 return ci_rid;
304 }
305}
306
307void TileAtlasView::_clear_material_canvas_items() {
308 for (KeyValue<Ref<Material>, RID> kv : material_tiles_draw) {
309 RS::get_singleton()->free(kv.value);
310 }
311 material_tiles_draw.clear();
312
313 for (KeyValue<Ref<Material>, RID> kv : material_alternatives_draw) {
314 RS::get_singleton()->free(kv.value);
315 }
316 material_alternatives_draw.clear();
317}
318
319void TileAtlasView::_draw_base_tiles_texture_grid() {
320 Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
321 if (texture.is_valid()) {
322 Vector2i margins = tile_set_atlas_source->get_margins();
323 Vector2i separation = tile_set_atlas_source->get_separation();
324 Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
325
326 Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
327
328 // Draw each tile texture region.
329 for (int x = 0; x < grid_size.x; x++) {
330 for (int y = 0; y < grid_size.y; y++) {
331 Vector2i origin = margins + (Vector2i(x, y) * (texture_region_size + separation));
332 Vector2i base_tile_coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y));
333 if (base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) {
334 if (base_tile_coords == Vector2i(x, y)) {
335 // Draw existing tile.
336 Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(base_tile_coords);
337 Vector2 region_size = texture_region_size * size_in_atlas + separation * (size_in_atlas - Vector2i(1, 1));
338 base_tiles_texture_grid->draw_rect(Rect2i(origin, region_size), Color(1.0, 1.0, 1.0, 0.8), false);
339 }
340 } else {
341 // Draw the grid.
342 base_tiles_texture_grid->draw_rect(Rect2i(origin, texture_region_size), Color(0.7, 0.7, 0.7, 0.1), false);
343 }
344 }
345 }
346 }
347}
348
349void TileAtlasView::_draw_base_tiles_shape_grid() {
350 // Draw the shapes.
351 Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color");
352 Vector2i tile_shape_size = tile_set->get_tile_size();
353 for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
354 Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
355 Vector2 in_tile_base_offset = tile_set_atlas_source->get_tile_data(tile_id, 0)->get_texture_origin();
356 if (tile_set_atlas_source->is_position_in_tile_texture_region(tile_id, 0, -tile_shape_size / 2) && tile_set_atlas_source->is_position_in_tile_texture_region(tile_id, 0, tile_shape_size / 2 - Vector2(1, 1))) {
357 for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(tile_id); frame++) {
358 Color color = grid_color;
359 if (frame > 0) {
360 color.a *= 0.3;
361 }
362 Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(tile_id, frame);
363 Transform2D tile_xform;
364 tile_xform.set_origin(Rect2(texture_region).get_center() + in_tile_base_offset);
365 tile_xform.set_scale(tile_shape_size);
366 tile_set->draw_tile_shape(base_tiles_shape_grid, tile_xform, color);
367 }
368 }
369 }
370}
371
372void TileAtlasView::_alternative_tiles_root_control_gui_input(const Ref<InputEvent> &p_event) {
373 alternative_tiles_root_control->set_tooltip_text("");
374
375 Ref<InputEventMouseMotion> mm = p_event;
376 if (mm.is_valid()) {
377 Transform2D xform = alternative_tiles_drawing_root->get_transform().affine_inverse();
378 Vector3i coords3 = get_alternative_tile_at_pos(xform.xform(mm->get_position()));
379 Vector2i coords = Vector2i(coords3.x, coords3.y);
380 int alternative_id = coords3.z;
381 if (coords != TileSetSource::INVALID_ATLAS_COORDS && alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE) {
382 alternative_tiles_root_control->set_tooltip_text(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: %d"), source_id, coords, alternative_id));
383 }
384 }
385}
386
387void TileAtlasView::_draw_alternatives() {
388 // Draw the alternative tiles.
389 Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
390 if (texture.is_valid()) {
391 Vector2 current_pos;
392 for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
393 Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i);
394 current_pos.x = 0;
395 int y_increment = 0;
396 Size2i texture_region_size = tile_set_atlas_source->get_tile_texture_region(atlas_coords).size;
397 int alternatives_count = tile_set_atlas_source->get_alternative_tiles_count(atlas_coords);
398 for (int j = 1; j < alternatives_count; j++) {
399 int alternative_id = tile_set_atlas_source->get_alternative_tile_id(atlas_coords, j);
400 TileData *tile_data = tile_set_atlas_source->get_tile_data(atlas_coords, alternative_id);
401 bool transposed = tile_data->get_transpose();
402
403 // Different materials need to be drawn with different CanvasItems.
404 RID ci_rid = _get_canvas_item_to_draw(tile_data, alternatives_draw, material_alternatives_draw);
405
406 // Update the y to max value.
407 Vector2i offset_pos;
408 if (transposed) {
409 offset_pos = (current_pos + Vector2(texture_region_size.y, texture_region_size.x) / 2 + tile_data->get_texture_origin());
410 y_increment = MAX(y_increment, texture_region_size.x);
411 } else {
412 offset_pos = (current_pos + texture_region_size / 2 + tile_data->get_texture_origin());
413 y_increment = MAX(y_increment, texture_region_size.y);
414 }
415
416 // Draw the tile.
417 TileMap::draw_tile(ci_rid, offset_pos, tile_set, source_id, atlas_coords, alternative_id);
418
419 // Increment the x position.
420 current_pos.x += transposed ? texture_region_size.y : texture_region_size.x;
421 }
422 if (alternatives_count > 1) {
423 current_pos.y += y_increment;
424 }
425 }
426 }
427}
428
429void TileAtlasView::_draw_background_left() {
430 background_left->draw_texture_rect(theme_cache.checkerboard, Rect2(Vector2(), background_left->get_size()), true);
431}
432
433void TileAtlasView::_draw_background_right() {
434 background_right->draw_texture_rect(theme_cache.checkerboard, Rect2(Vector2(), background_right->get_size()), true);
435}
436
437void TileAtlasView::set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id) {
438 tile_set = p_tile_set;
439 tile_set_atlas_source = p_tile_set_atlas_source;
440
441 _clear_material_canvas_items();
442
443 if (!tile_set) {
444 return;
445 }
446
447 ERR_FAIL_COND(p_source_id < 0);
448 ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source);
449
450 source_id = p_source_id;
451
452 // Show or hide the view.
453 bool valid = tile_set_atlas_source->get_texture().is_valid();
454 hbox->set_visible(valid);
455 missing_source_label->set_visible(!valid);
456
457 // Update the rect cache.
458 _update_alternative_tiles_rect_cache();
459
460 // Update everything.
461 _update_zoom_and_panning();
462
463 base_tiles_drawing_root->set_size(_compute_base_tiles_control_size());
464 alternative_tiles_drawing_root->set_size(_compute_alternative_tiles_control_size());
465
466 // Update.
467 base_tiles_draw->queue_redraw();
468 base_tiles_texture_grid->queue_redraw();
469 base_tiles_shape_grid->queue_redraw();
470 alternatives_draw->queue_redraw();
471 background_left->queue_redraw();
472 background_right->queue_redraw();
473}
474
475float TileAtlasView::get_zoom() const {
476 return zoom_widget->get_zoom();
477};
478
479void TileAtlasView::set_transform(float p_zoom, Vector2i p_panning) {
480 zoom_widget->set_zoom(p_zoom);
481 panning = p_panning;
482 _update_zoom_and_panning();
483};
484
485void TileAtlasView::set_padding(Side p_side, int p_padding) {
486 ERR_FAIL_COND(p_padding < 0);
487 margin_container_paddings[p_side] = p_padding;
488}
489
490Vector2i TileAtlasView::get_atlas_tile_coords_at_pos(const Vector2 p_pos, bool p_clamp) const {
491 Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
492 if (!texture.is_valid()) {
493 return TileSetSource::INVALID_ATLAS_COORDS;
494 }
495
496 Vector2i margins = tile_set_atlas_source->get_margins();
497 Vector2i separation = tile_set_atlas_source->get_separation();
498 Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
499
500 // Compute index in atlas.
501 Vector2 pos = p_pos - margins;
502 Vector2i ret = (pos / (texture_region_size + separation)).floor();
503
504 // Clamp.
505 if (p_clamp) {
506 Vector2i size = tile_set_atlas_source->get_atlas_grid_size();
507 ret.x = CLAMP(ret.x, 0, size.x - 1);
508 ret.y = CLAMP(ret.y, 0, size.y - 1);
509 }
510
511 return ret;
512}
513
514void TileAtlasView::_update_alternative_tiles_rect_cache() {
515 alternative_tiles_rect_cache.clear();
516
517 Rect2i current;
518 for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
519 Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
520 int alternatives_count = tile_set_atlas_source->get_alternative_tiles_count(tile_id);
521 Size2i texture_region_size = tile_set_atlas_source->get_tile_texture_region(tile_id).size;
522 int line_height = 0;
523 for (int j = 1; j < alternatives_count; j++) {
524 int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j);
525 TileData *tile_data = tile_set_atlas_source->get_tile_data(tile_id, alternative_id);
526 bool transposed = tile_data->get_transpose();
527 current.size = transposed ? Vector2i(texture_region_size.y, texture_region_size.x) : texture_region_size;
528
529 // Update the rect.
530 if (!alternative_tiles_rect_cache.has(tile_id)) {
531 alternative_tiles_rect_cache[tile_id] = HashMap<int, Rect2i>();
532 }
533 alternative_tiles_rect_cache[tile_id][alternative_id] = current;
534
535 current.position.x += transposed ? texture_region_size.y : texture_region_size.x;
536 line_height = MAX(line_height, transposed ? texture_region_size.x : texture_region_size.y);
537 }
538
539 current.position.x = 0;
540 current.position.y += line_height;
541 }
542}
543
544Vector3i TileAtlasView::get_alternative_tile_at_pos(const Vector2 p_pos) const {
545 for (const KeyValue<Vector2, HashMap<int, Rect2i>> &E_coords : alternative_tiles_rect_cache) {
546 for (const KeyValue<int, Rect2i> &E_alternative : E_coords.value) {
547 if (E_alternative.value.has_point(p_pos)) {
548 return Vector3i(E_coords.key.x, E_coords.key.y, E_alternative.key);
549 }
550 }
551 }
552
553 return Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE);
554}
555
556Rect2i TileAtlasView::get_alternative_tile_rect(const Vector2i p_coords, int p_alternative_tile) {
557 ERR_FAIL_COND_V_MSG(!alternative_tiles_rect_cache.has(p_coords), Rect2i(), vformat("No cached rect for tile coords:%s", p_coords));
558 ERR_FAIL_COND_V_MSG(!alternative_tiles_rect_cache[p_coords].has(p_alternative_tile), Rect2i(), vformat("No cached rect for tile coords:%s alternative_id:%d", p_coords, p_alternative_tile));
559
560 return alternative_tiles_rect_cache[p_coords][p_alternative_tile];
561}
562
563void TileAtlasView::queue_redraw() {
564 base_tiles_draw->queue_redraw();
565 base_tiles_texture_grid->queue_redraw();
566 base_tiles_shape_grid->queue_redraw();
567 alternatives_draw->queue_redraw();
568 background_left->queue_redraw();
569 background_right->queue_redraw();
570}
571
572void TileAtlasView::_update_theme_item_cache() {
573 Control::_update_theme_item_cache();
574
575 theme_cache.center_view_icon = get_editor_theme_icon(SNAME("CenterView"));
576 theme_cache.checkerboard = get_editor_theme_icon(SNAME("Checkerboard"));
577}
578
579void TileAtlasView::_notification(int p_what) {
580 switch (p_what) {
581 case NOTIFICATION_ENTER_TREE:
582 case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
583 panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning")));
584 } break;
585
586 case NOTIFICATION_THEME_CHANGED: {
587 button_center_view->set_icon(theme_cache.center_view_icon);
588 } break;
589 }
590}
591
592void TileAtlasView::_bind_methods() {
593 ADD_SIGNAL(MethodInfo("transform_changed", PropertyInfo(Variant::FLOAT, "zoom"), PropertyInfo(Variant::VECTOR2, "scroll")));
594}
595
596TileAtlasView::TileAtlasView() {
597 set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
598
599 Panel *panel = memnew(Panel);
600 panel->set_clip_contents(true);
601 panel->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
602 panel->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
603 panel->set_h_size_flags(SIZE_EXPAND_FILL);
604 panel->set_v_size_flags(SIZE_EXPAND_FILL);
605 add_child(panel);
606
607 zoom_widget = memnew(EditorZoomWidget);
608 add_child(zoom_widget);
609 zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE);
610 zoom_widget->connect("zoom_changed", callable_mp(this, &TileAtlasView::_zoom_widget_changed).unbind(1));
611 zoom_widget->set_shortcut_context(this);
612
613 button_center_view = memnew(Button);
614 button_center_view->set_anchors_and_offsets_preset(Control::PRESET_TOP_RIGHT, Control::PRESET_MODE_MINSIZE, 5);
615 button_center_view->set_grow_direction_preset(Control::PRESET_TOP_RIGHT);
616 button_center_view->connect("pressed", callable_mp(this, &TileAtlasView::_center_view));
617 button_center_view->set_flat(true);
618 button_center_view->set_disabled(true);
619 button_center_view->set_tooltip_text(TTR("Center View"));
620 add_child(button_center_view);
621
622 panner.instantiate();
623 panner->set_callbacks(callable_mp(this, &TileAtlasView::_pan_callback), callable_mp(this, &TileAtlasView::_zoom_callback));
624 panner->set_enable_rmb(true);
625
626 center_container = memnew(CenterContainer);
627 center_container->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
628 center_container->set_anchors_preset(Control::PRESET_CENTER);
629 center_container->connect("gui_input", callable_mp(this, &TileAtlasView::gui_input));
630 center_container->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key));
631 center_container->set_focus_mode(FOCUS_CLICK);
632 panel->add_child(center_container);
633
634 missing_source_label = memnew(Label);
635 missing_source_label->set_text(TTR("The selected atlas source has no valid texture. Assign a texture in the TileSet bottom tab."));
636 center_container->add_child(missing_source_label);
637
638 margin_container = memnew(MarginContainer);
639 margin_container->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
640 center_container->add_child(margin_container);
641
642 hbox = memnew(HBoxContainer);
643 hbox->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
644 hbox->add_theme_constant_override("separation", 10);
645 hbox->hide();
646 margin_container->add_child(hbox);
647
648 VBoxContainer *left_vbox = memnew(VBoxContainer);
649 left_vbox->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
650 hbox->add_child(left_vbox);
651
652 VBoxContainer *right_vbox = memnew(VBoxContainer);
653 right_vbox->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
654 hbox->add_child(right_vbox);
655
656 // Base tiles.
657 Label *base_tile_label = memnew(Label);
658 base_tile_label->set_mouse_filter(Control::MOUSE_FILTER_PASS);
659 base_tile_label->set_text(TTR("Base Tiles"));
660 base_tile_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
661 left_vbox->add_child(base_tile_label);
662
663 base_tiles_root_control = memnew(Control);
664 base_tiles_root_control->set_mouse_filter(Control::MOUSE_FILTER_PASS);
665 base_tiles_root_control->set_v_size_flags(Control::SIZE_EXPAND_FILL);
666 base_tiles_root_control->connect("gui_input", callable_mp(this, &TileAtlasView::_base_tiles_root_control_gui_input));
667 left_vbox->add_child(base_tiles_root_control);
668
669 background_left = memnew(Control);
670 background_left->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
671 background_left->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT);
672 background_left->set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED);
673 background_left->connect("draw", callable_mp(this, &TileAtlasView::_draw_background_left));
674 base_tiles_root_control->add_child(background_left);
675
676 base_tiles_drawing_root = memnew(Control);
677 base_tiles_drawing_root->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
678 base_tiles_drawing_root->set_texture_filter(TEXTURE_FILTER_NEAREST);
679 base_tiles_root_control->add_child(base_tiles_drawing_root);
680
681 base_tiles_draw = memnew(Control);
682 base_tiles_draw->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
683 base_tiles_draw->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
684 base_tiles_draw->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles));
685 base_tiles_drawing_root->add_child(base_tiles_draw);
686
687 base_tiles_texture_grid = memnew(Control);
688 base_tiles_texture_grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
689 base_tiles_texture_grid->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
690 base_tiles_texture_grid->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_texture_grid));
691 base_tiles_drawing_root->add_child(base_tiles_texture_grid);
692
693 base_tiles_shape_grid = memnew(Control);
694 base_tiles_shape_grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
695 base_tiles_shape_grid->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
696 base_tiles_shape_grid->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_shape_grid));
697 base_tiles_drawing_root->add_child(base_tiles_shape_grid);
698
699 // Alternative tiles.
700 Label *alternative_tiles_label = memnew(Label);
701 alternative_tiles_label->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
702 alternative_tiles_label->set_text(TTR("Alternative Tiles"));
703 alternative_tiles_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
704 right_vbox->add_child(alternative_tiles_label);
705
706 alternative_tiles_root_control = memnew(Control);
707 alternative_tiles_root_control->set_mouse_filter(Control::MOUSE_FILTER_PASS);
708 alternative_tiles_root_control->set_v_size_flags(Control::SIZE_EXPAND_FILL);
709 alternative_tiles_root_control->connect("gui_input", callable_mp(this, &TileAtlasView::_alternative_tiles_root_control_gui_input));
710 right_vbox->add_child(alternative_tiles_root_control);
711
712 background_right = memnew(Control);
713 background_right->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
714 background_right->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT);
715 background_right->set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED);
716 background_right->connect("draw", callable_mp(this, &TileAtlasView::_draw_background_right));
717 alternative_tiles_root_control->add_child(background_right);
718
719 alternative_tiles_drawing_root = memnew(Control);
720 alternative_tiles_drawing_root->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
721 alternative_tiles_drawing_root->set_texture_filter(TEXTURE_FILTER_NEAREST);
722 alternative_tiles_root_control->add_child(alternative_tiles_drawing_root);
723
724 alternatives_draw = memnew(Control);
725 alternatives_draw->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
726 alternatives_draw->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
727 alternatives_draw->connect("draw", callable_mp(this, &TileAtlasView::_draw_alternatives));
728 alternative_tiles_drawing_root->add_child(alternatives_draw);
729}
730
731TileAtlasView::~TileAtlasView() {
732 _clear_material_canvas_items();
733}
734