1 /*************************************************************************/
2 /* tile_map_editor_plugin.cpp */
3 /*************************************************************************/
4 /* This file is part of: */
5 /* GODOT ENGINE */
6 /* https://godotengine.org */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
9 /* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
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_map_editor_plugin.h"
32
33 #include "os/input.h"
34 #include "os/keyboard.h"
35
36 #include "canvas_item_editor_plugin.h"
37 #include "editor/editor_scale.h"
38 #include "editor/editor_settings.h"
39
_notification(int p_what)40 void TileMapEditor::_notification(int p_what) {
41
42 switch (p_what) {
43
44 case NOTIFICATION_ENTER_TREE: {
45
46 transp->set_icon(get_icon("Transpose", "EditorIcons"));
47 mirror_x->set_icon(get_icon("MirrorX", "EditorIcons"));
48 mirror_y->set_icon(get_icon("MirrorY", "EditorIcons"));
49 rotate_0->set_icon(get_icon("Rotate0", "EditorIcons"));
50 rotate_90->set_icon(get_icon("Rotate90", "EditorIcons"));
51 rotate_180->set_icon(get_icon("Rotate180", "EditorIcons"));
52 rotate_270->set_icon(get_icon("Rotate270", "EditorIcons"));
53
54 } break;
55
56 case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
57
58 bool new_show_tile_info = EditorSettings::get_singleton()->get("tile_map/show_tile_info_on_hover");
59 if (new_show_tile_info != show_tile_info) {
60 show_tile_info = new_show_tile_info;
61 tile_info->set_hidden(!show_tile_info);
62 }
63
64 if (is_visible()) {
65 _update_palette();
66 }
67 } break;
68 }
69 }
70
_menu_option(int p_option)71 void TileMapEditor::_menu_option(int p_option) {
72
73 switch (p_option) {
74
75 case OPTION_BUCKET: {
76
77 tool = TOOL_BUCKET;
78
79 canvas_item_editor->update();
80 } break;
81 case OPTION_PICK_TILE: {
82
83 tool = TOOL_PICKING;
84
85 canvas_item_editor->update();
86 } break;
87 case OPTION_SELECT: {
88
89 tool = TOOL_SELECTING;
90 selection_active = false;
91
92 canvas_item_editor->update();
93 } break;
94 case OPTION_DUPLICATE: {
95
96 _update_copydata();
97
98 if (selection_active) {
99 tool = TOOL_DUPLICATING;
100
101 canvas_item_editor->update();
102 }
103 } break;
104 case OPTION_ERASE_SELECTION: {
105
106 if (!selection_active)
107 return;
108
109 undo_redo->create_action("Erase Selection");
110 for (int i = rectangle.pos.y; i <= rectangle.pos.y + rectangle.size.y; i++) {
111 for (int j = rectangle.pos.x; j <= rectangle.pos.x + rectangle.size.x; j++) {
112
113 _set_cell(Point2i(j, i), TileMap::INVALID_CELL, false, false, false, true);
114 }
115 }
116 undo_redo->commit_action();
117
118 selection_active = false;
119 copydata.clear();
120
121 canvas_item_editor->update();
122 } break;
123 }
124 }
125
_canvas_mouse_enter()126 void TileMapEditor::_canvas_mouse_enter() {
127
128 mouse_over = true;
129 canvas_item_editor->update();
130 }
131
_canvas_mouse_exit()132 void TileMapEditor::_canvas_mouse_exit() {
133
134 mouse_over = false;
135 canvas_item_editor->update();
136 }
137
get_selected_tile() const138 int TileMapEditor::get_selected_tile() const {
139
140 int item = palette->get_current();
141
142 if (item == -1)
143 return TileMap::INVALID_CELL;
144
145 return palette->get_item_metadata(item);
146 }
147
set_selected_tile(int p_tile)148 void TileMapEditor::set_selected_tile(int p_tile) {
149
150 int idx = palette->find_metadata(p_tile);
151
152 if (idx >= 0) {
153 palette->select(idx, true);
154 palette->ensure_current_is_visible();
155 }
156 }
157
_set_cell(const Point2i & p_pos,int p_value,bool p_flip_h,bool p_flip_v,bool p_transpose,bool p_with_undo)158 void TileMapEditor::_set_cell(const Point2i &p_pos, int p_value, bool p_flip_h, bool p_flip_v, bool p_transpose, bool p_with_undo) {
159
160 ERR_FAIL_COND(!node);
161
162 int prev_val = node->get_cell(p_pos.x, p_pos.y);
163
164 bool prev_flip_h = node->is_cell_x_flipped(p_pos.x, p_pos.y);
165 bool prev_flip_v = node->is_cell_y_flipped(p_pos.x, p_pos.y);
166 bool prev_transpose = node->is_cell_transposed(p_pos.x, p_pos.y);
167
168 if (p_value == prev_val && p_flip_h == prev_flip_h && p_flip_v == prev_flip_v && p_transpose == prev_transpose)
169 return; //check that it's actually different
170
171 if (p_with_undo) {
172
173 undo_redo->add_do_method(node, "set_cellv", Point2(p_pos), p_value, p_flip_h, p_flip_v, p_transpose);
174 undo_redo->add_undo_method(node, "set_cellv", Point2(p_pos), prev_val, prev_flip_h, prev_flip_v, prev_transpose);
175 } else {
176
177 node->set_cell(p_pos.x, p_pos.y, p_value, p_flip_h, p_flip_v, p_transpose);
178 }
179 }
180
_text_entered(const String & p_text)181 void TileMapEditor::_text_entered(const String &p_text) {
182
183 canvas_item_editor->grab_focus();
184 }
185
_text_changed(const String & p_text)186 void TileMapEditor::_text_changed(const String &p_text) {
187
188 _update_palette();
189 }
190
_sbox_input(const InputEvent & p_ie)191 void TileMapEditor::_sbox_input(const InputEvent &p_ie) {
192
193 if (p_ie.type == InputEvent::KEY && (p_ie.key.scancode == KEY_UP ||
194 p_ie.key.scancode == KEY_DOWN ||
195 p_ie.key.scancode == KEY_PAGEUP ||
196 p_ie.key.scancode == KEY_PAGEDOWN)) {
197
198 palette->call("_input_event", p_ie);
199 search_box->accept_event();
200 }
201 }
202
203 // Implementation detail of TileMapEditor::_update_palette();
204 // in modern C++ this could have been inside its body
205 namespace {
206 struct _PaletteEntry {
207 int id;
208 String name;
209
operator <__anon2d0e0c140111::_PaletteEntry210 bool operator<(const _PaletteEntry &p_rhs) const {
211 return name < p_rhs.name;
212 }
213 };
214 } // namespace
215
_update_palette()216 void TileMapEditor::_update_palette() {
217
218 if (!node)
219 return;
220
221 int selected = get_selected_tile();
222 palette->clear();
223
224 Ref<TileSet> tileset = node->get_tileset();
225 if (tileset.is_null())
226 return;
227
228 List<int> tiles;
229 tileset->get_tile_list(&tiles);
230
231 if (tiles.empty())
232 return;
233
234 float min_size = EDITOR_DEF("tile_map/preview_size", 64);
235 min_size *= EDSCALE;
236 int hseparation = EDITOR_DEF("tile_map/palette_item_hseparation", 8);
237 bool show_tile_names = bool(EDITOR_DEF("tile_map/show_tile_names", true));
238 bool show_tile_ids = bool(EDITOR_DEF("tile_map/show_tile_ids", true));
239 bool sort_by_name = bool(EDITOR_DEF("tile_map/sort_tiles_by_name", false));
240
241 palette->add_constant_override("hseparation", hseparation * EDSCALE);
242 palette->add_constant_override("vseparation", 8 * EDSCALE);
243
244 palette->set_fixed_icon_size(Size2(min_size, min_size));
245 palette->set_fixed_column_width(min_size * MAX(size_slider->get_val(), 1));
246
247 String filter = search_box->get_text().strip_edges();
248
249 Vector<_PaletteEntry> entries;
250
251 for (List<int>::Element *E = tiles.front(); E; E = E->next()) {
252
253 String name = tileset->tile_get_name(E->get());
254
255 if (name != "") {
256 if (show_tile_ids) {
257 if (sort_by_name) {
258 name = name + " - " + itos(E->get());
259 } else {
260 name = itos(E->get()) + " - " + name;
261 }
262 }
263 } else {
264 name = "#" + itos(E->get());
265 }
266
267 if (filter != "" && !filter.is_subsequence_ofi(name))
268 continue;
269
270 const _PaletteEntry entry = { E->get(), name };
271 entries.push_back(entry);
272 }
273
274 if (sort_by_name) {
275 entries.sort();
276 }
277
278 for (int i = 0; i < entries.size(); i++) {
279
280 if (show_tile_names) {
281 palette->add_item(entries[i].name);
282 } else {
283 palette->add_item(String());
284 }
285
286 Ref<Texture> tex = tileset->tile_get_texture(entries[i].id);
287
288 if (tex.is_valid()) {
289 Rect2 region = tileset->tile_get_region(entries[i].id);
290
291 if (!region.has_no_area())
292 palette->set_item_icon_region(palette->get_item_count() - 1, region);
293
294 palette->set_item_icon(palette->get_item_count() - 1, tex);
295 }
296
297 palette->set_item_metadata(palette->get_item_count() - 1, entries[i].id);
298 }
299
300 palette->set_same_column_width(true);
301
302 if (selected != -1)
303 set_selected_tile(selected);
304 else
305 palette->select(0);
306 }
307
_pick_tile(const Point2 & p_pos)308 void TileMapEditor::_pick_tile(const Point2 &p_pos) {
309
310 int id = node->get_cell(p_pos.x, p_pos.y);
311
312 if (id == TileMap::INVALID_CELL)
313 return;
314
315 if (search_box->get_text().strip_edges() != "") {
316
317 search_box->set_text("");
318 _update_palette();
319 }
320
321 set_selected_tile(id);
322
323 mirror_x->set_pressed(node->is_cell_x_flipped(p_pos.x, p_pos.y));
324 mirror_y->set_pressed(node->is_cell_y_flipped(p_pos.x, p_pos.y));
325 transp->set_pressed(node->is_cell_transposed(p_pos.x, p_pos.y));
326
327 _update_transform_buttons();
328 canvas_item_editor->update();
329 }
330
_bucket_fill(const Point2i & p_start,bool erase,bool preview)331 DVector<Vector2> TileMapEditor::_bucket_fill(const Point2i &p_start, bool erase, bool preview) {
332
333 int prev_id = node->get_cell(p_start.x, p_start.y);
334 int id = TileMap::INVALID_CELL;
335 if (!erase) {
336 id = get_selected_tile();
337
338 if (id == TileMap::INVALID_CELL)
339 return DVector<Vector2>();
340 } else if (prev_id == TileMap::INVALID_CELL) {
341 return DVector<Vector2>();
342 }
343
344 if (id == prev_id) {
345 return DVector<Vector2>();
346 }
347
348 Rect2i r = node->get_item_rect();
349 r.pos = r.pos / node->get_cell_size();
350 r.size = r.size / node->get_cell_size();
351
352 int area = r.get_area();
353 if (preview) {
354 // Test if we can re-use the result from preview bucket fill
355 bool invalidate_cache = false;
356 // Area changed
357 if (r != bucket_cache_rect)
358 _clear_bucket_cache();
359 // Cache grid is not initialized
360 if (bucket_cache_visited == 0) {
361 bucket_cache_visited = new bool[area];
362 invalidate_cache = true;
363 }
364 // Tile ID changed or position wasn't visited by the previous fill
365 int loc = (p_start.x - r.get_pos().x) + (p_start.y - r.get_pos().y) * r.get_size().x;
366 if (prev_id != bucket_cache_tile || !bucket_cache_visited[loc]) {
367 invalidate_cache = true;
368 }
369 if (invalidate_cache) {
370 for (int i = 0; i < area; ++i)
371 bucket_cache_visited[i] = false;
372 bucket_cache = DVector<Vector2>();
373 bucket_cache_tile = prev_id;
374 bucket_cache_rect = r;
375 } else {
376 return bucket_cache;
377 }
378 }
379
380 DVector<Vector2> points;
381
382 List<Point2i> queue;
383 queue.push_back(p_start);
384
385 while (queue.size()) {
386
387 Point2i n = queue.front()->get();
388 queue.pop_front();
389
390 if (!r.has_point(n))
391 continue;
392
393 if (node->get_cell(n.x, n.y) == prev_id) {
394
395 if (preview) {
396 int loc = (n.x - r.get_pos().x) + (n.y - r.get_pos().y) * r.get_size().x;
397 if (bucket_cache_visited[loc])
398 continue;
399 bucket_cache_visited[loc] = true;
400 bucket_cache.push_back(n);
401 } else {
402 node->set_cellv(n, id, flip_h, flip_v, transpose);
403 points.push_back(n);
404 }
405
406 queue.push_back(n + Point2i(0, 1));
407 queue.push_back(n + Point2i(0, -1));
408 queue.push_back(n + Point2i(1, 0));
409 queue.push_back(n + Point2i(-1, 0));
410 }
411 }
412
413 return preview ? bucket_cache : points;
414 }
415
_fill_points(const DVector<Vector2> p_points,const Dictionary & p_op)416 void TileMapEditor::_fill_points(const DVector<Vector2> p_points, const Dictionary &p_op) {
417
418 int len = p_points.size();
419 DVector<Vector2>::Read pr = p_points.read();
420
421 int id = p_op["id"];
422 bool xf = p_op["flip_h"];
423 bool yf = p_op["flip_v"];
424 bool tr = p_op["transpose"];
425
426 for (int i = 0; i < len; i++) {
427
428 _set_cell(pr[i], id, xf, yf, tr);
429 }
430 }
431
_erase_points(const DVector<Vector2> p_points)432 void TileMapEditor::_erase_points(const DVector<Vector2> p_points) {
433
434 int len = p_points.size();
435 DVector<Vector2>::Read pr = p_points.read();
436
437 for (int i = 0; i < len; i++) {
438
439 _set_cell(pr[i], TileMap::INVALID_CELL);
440 }
441 }
442
_select(const Point2i & p_from,const Point2i & p_to)443 void TileMapEditor::_select(const Point2i &p_from, const Point2i &p_to) {
444
445 Point2i begin = p_from;
446 Point2i end = p_to;
447
448 if (begin.x > end.x) {
449
450 SWAP(begin.x, end.x);
451 }
452 if (begin.y > end.y) {
453
454 SWAP(begin.y, end.y);
455 }
456
457 rectangle.pos = begin;
458 rectangle.size = end - begin;
459
460 canvas_item_editor->update();
461 }
462
_draw_cell(int p_cell,const Point2i & p_point,bool p_flip_h,bool p_flip_v,bool p_transpose,const Matrix32 & p_xform)463 void TileMapEditor::_draw_cell(int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Matrix32 &p_xform) {
464
465 Ref<Texture> t = node->get_tileset()->tile_get_texture(p_cell);
466
467 if (t.is_null())
468 return;
469
470 Vector2 tile_ofs = node->get_tileset()->tile_get_texture_offset(p_cell);
471
472 Rect2 r = node->get_tileset()->tile_get_region(p_cell);
473 Size2 sc = p_xform.get_scale();
474
475 Rect2 rect = Rect2();
476 rect.pos = node->map_to_world(p_point) + node->get_cell_draw_offset();
477
478 if (r.has_no_area()) {
479 rect.size = t->get_size();
480 } else {
481 rect.size = r.size;
482 }
483
484 if (rect.size.y > rect.size.x) {
485 if ((p_flip_h && (p_flip_v || p_transpose)) || (p_flip_v && !p_transpose))
486 tile_ofs.y += rect.size.y - rect.size.x;
487 } else if (rect.size.y < rect.size.x) {
488 if ((p_flip_v && (p_flip_h || p_transpose)) || (p_flip_h && !p_transpose))
489 tile_ofs.x += rect.size.x - rect.size.y;
490 }
491
492 if (p_transpose) {
493 SWAP(tile_ofs.x, tile_ofs.y);
494 }
495 if (p_flip_h) {
496 sc.x *= -1.0;
497 tile_ofs.x *= -1.0;
498 }
499 if (p_flip_v) {
500 sc.y *= -1.0;
501 tile_ofs.y *= -1.0;
502 }
503
504 if (node->get_tile_origin() == TileMap::TILE_ORIGIN_TOP_LEFT) {
505
506 rect.pos += tile_ofs;
507 } else if (node->get_tile_origin() == TileMap::TILE_ORIGIN_BOTTOM_LEFT) {
508 Size2 cell_size = node->get_cell_size();
509
510 rect.pos += tile_ofs;
511
512 if (p_transpose) {
513 if (p_flip_h)
514 rect.pos.x -= cell_size.x;
515 else
516 rect.pos.x += cell_size.x;
517 } else {
518 if (p_flip_v)
519 rect.pos.y -= cell_size.y;
520 else
521 rect.pos.y += cell_size.y;
522 }
523
524 } else if (node->get_tile_origin() == TileMap::TILE_ORIGIN_CENTER) {
525 rect.pos += node->get_cell_size() / 2;
526 Vector2 s = r.size;
527
528 Vector2 center = (s / 2) - tile_ofs;
529
530 if (p_flip_h)
531 rect.pos.x -= s.x - center.x;
532 else
533 rect.pos.x -= center.x;
534
535 if (p_flip_v)
536 rect.pos.y -= s.y - center.y;
537 else
538 rect.pos.y -= center.y;
539 }
540
541 rect.pos = p_xform.xform(rect.pos);
542 rect.size *= sc;
543
544 if (r.has_no_area())
545 canvas_item_editor->draw_texture_rect(t, rect, false, Color(1, 1, 1, 0.5), p_transpose);
546 else
547 canvas_item_editor->draw_texture_rect_region(t, rect, r, Color(1, 1, 1, 0.5), p_transpose);
548 }
549
_draw_fill_preview(int p_cell,const Point2i & p_point,bool p_flip_h,bool p_flip_v,bool p_transpose,const Matrix32 & p_xform)550 void TileMapEditor::_draw_fill_preview(int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Matrix32 &p_xform) {
551
552 DVector<Vector2> points = _bucket_fill(p_point, false, true);
553 DVector<Vector2>::Read pr = points.read();
554 int len = points.size();
555 int time_after = OS::get_singleton()->get_ticks_msec();
556
557 for (int i = 0; i < len; ++i) {
558 _draw_cell(p_cell, pr[i], p_flip_h, p_flip_v, p_transpose, p_xform);
559 }
560 }
561
_clear_bucket_cache()562 void TileMapEditor::_clear_bucket_cache() {
563 if (bucket_cache_visited) {
564 delete[] bucket_cache_visited;
565 bucket_cache_visited = 0;
566 }
567 }
568
_update_copydata()569 void TileMapEditor::_update_copydata() {
570
571 copydata.clear();
572
573 if (!selection_active)
574 return;
575
576 for (int i = rectangle.pos.y; i <= rectangle.pos.y + rectangle.size.y; i++) {
577
578 for (int j = rectangle.pos.x; j <= rectangle.pos.x + rectangle.size.x; j++) {
579
580 TileData tcd;
581
582 tcd.cell = node->get_cell(j, i);
583
584 if (tcd.cell != TileMap::INVALID_CELL) {
585 tcd.pos = Point2i(j, i);
586 tcd.flip_h = node->is_cell_x_flipped(j, i);
587 tcd.flip_v = node->is_cell_y_flipped(j, i);
588 tcd.transpose = node->is_cell_transposed(j, i);
589 }
590
591 copydata.push_back(tcd);
592 }
593 }
594 }
595
line(int x0,int x1,int y0,int y1)596 static inline Vector<Point2i> line(int x0, int x1, int y0, int y1) {
597
598 Vector<Point2i> points;
599
600 float dx = ABS(x1 - x0);
601 float dy = ABS(y1 - y0);
602
603 int x = x0;
604 int y = y0;
605
606 int sx = x0 > x1 ? -1 : 1;
607 int sy = y0 > y1 ? -1 : 1;
608
609 if (dx > dy) {
610 float err = dx / 2;
611
612 for (; x != x1; x += sx) {
613 points.push_back(Vector2(x, y));
614
615 err -= dy;
616 if (err < 0) {
617 y += sy;
618 err += dx;
619 }
620 }
621 } else {
622 float err = dy / 2;
623
624 for (; y != y1; y += sy) {
625 points.push_back(Vector2(x, y));
626
627 err -= dx;
628 if (err < 0) {
629 x += sx;
630 err += dy;
631 }
632 }
633 }
634
635 points.push_back(Vector2(x, y));
636
637 return points;
638 }
639
forward_input_event(const InputEvent & p_event)640 bool TileMapEditor::forward_input_event(const InputEvent &p_event) {
641
642 if (!node || !node->get_tileset().is_valid() || !node->is_visible())
643 return false;
644
645 Matrix32 xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * node->get_global_transform();
646 Matrix32 xform_inv = xform.affine_inverse();
647
648 switch (p_event.type) {
649
650 case InputEvent::MOUSE_BUTTON: {
651
652 const InputEventMouseButton &mb = p_event.mouse_button;
653
654 if (mb.button_index == BUTTON_LEFT) {
655
656 if (mb.pressed) {
657
658 if (Input::get_singleton()->is_key_pressed(KEY_SPACE))
659 return false; //drag
660
661 if (tool == TOOL_NONE) {
662
663 if (mb.mod.shift) {
664
665 if (mb.mod.control)
666 tool = TOOL_RECTANGLE_PAINT;
667 else
668 tool = TOOL_LINE_PAINT;
669
670 selection_active = false;
671 rectangle_begin = over_tile;
672
673 return true;
674 }
675
676 if (mb.mod.control) {
677
678 tool = TOOL_PICKING;
679 _pick_tile(over_tile);
680
681 return true;
682 }
683
684 tool = TOOL_PAINTING;
685 }
686
687 if (tool == TOOL_PAINTING) {
688
689 int id = get_selected_tile();
690
691 if (id != TileMap::INVALID_CELL) {
692
693 tool = TOOL_PAINTING;
694
695 paint_undo.clear();
696 paint_undo[over_tile] = _get_op_from_cell(over_tile);
697
698 _set_cell(over_tile, id, flip_h, flip_v, transpose);
699 }
700 } else if (tool == TOOL_PICKING) {
701
702 _pick_tile(over_tile);
703 } else if (tool == TOOL_SELECTING) {
704
705 selection_active = true;
706 rectangle_begin = over_tile;
707 }
708
709 return true;
710
711 } else {
712
713 if (tool != TOOL_NONE) {
714
715 if (tool == TOOL_PAINTING) {
716
717 int id = get_selected_tile();
718
719 if (id != TileMap::INVALID_CELL && paint_undo.size()) {
720
721 undo_redo->create_action(TTR("Paint TileMap"));
722 for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) {
723
724 Point2 p = E->key();
725 undo_redo->add_do_method(node, "set_cellv", p, id, flip_h, flip_v, transpose);
726 undo_redo->add_undo_method(node, "set_cellv", p, E->get().idx, E->get().xf, E->get().yf, E->get().tr);
727 }
728 undo_redo->commit_action();
729
730 paint_undo.clear();
731 }
732 } else if (tool == TOOL_LINE_PAINT) {
733
734 int id = get_selected_tile();
735
736 if (id != TileMap::INVALID_CELL) {
737
738 undo_redo->create_action("Line Draw");
739 for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) {
740
741 _set_cell(E->key(), id, flip_h, flip_v, transpose, true);
742 }
743 undo_redo->commit_action();
744
745 paint_undo.clear();
746
747 canvas_item_editor->update();
748 }
749 } else if (tool == TOOL_RECTANGLE_PAINT) {
750
751 int id = get_selected_tile();
752
753 if (id != TileMap::INVALID_CELL) {
754
755 undo_redo->create_action("Rectangle Paint");
756 for (int i = rectangle.pos.y; i <= rectangle.pos.y + rectangle.size.y; i++) {
757 for (int j = rectangle.pos.x; j <= rectangle.pos.x + rectangle.size.x; j++) {
758
759 _set_cell(Point2i(j, i), id, flip_h, flip_v, transpose, true);
760 }
761 }
762 undo_redo->commit_action();
763
764 canvas_item_editor->update();
765 }
766 } else if (tool == TOOL_DUPLICATING) {
767
768 Point2 ofs = over_tile - rectangle.pos;
769
770 undo_redo->create_action(TTR("Duplicate"));
771 for (List<TileData>::Element *E = copydata.front(); E; E = E->next()) {
772
773 _set_cell(E->get().pos + ofs, E->get().cell, E->get().flip_h, E->get().flip_v, E->get().transpose, true);
774 }
775 undo_redo->commit_action();
776
777 copydata.clear();
778
779 canvas_item_editor->update();
780
781 } else if (tool == TOOL_SELECTING) {
782
783 canvas_item_editor->update();
784
785 } else if (tool == TOOL_BUCKET) {
786
787 Dictionary pop;
788 pop["id"] = node->get_cell(over_tile.x, over_tile.y);
789 pop["flip_h"] = node->is_cell_x_flipped(over_tile.x, over_tile.y);
790 pop["flip_v"] = node->is_cell_y_flipped(over_tile.x, over_tile.y);
791 pop["transpose"] = node->is_cell_transposed(over_tile.x, over_tile.y);
792
793 DVector<Vector2> points = _bucket_fill(over_tile);
794
795 if (points.size() == 0)
796 return false;
797
798 Dictionary op;
799 op["id"] = get_selected_tile();
800 op["flip_h"] = flip_h;
801 op["flip_v"] = flip_v;
802 op["transpose"] = transpose;
803
804 undo_redo->create_action("Bucket Fill");
805
806 undo_redo->add_do_method(this, "_fill_points", points, op);
807 undo_redo->add_undo_method(this, "_fill_points", points, pop);
808
809 undo_redo->commit_action();
810 }
811
812 tool = TOOL_NONE;
813
814 return true;
815 }
816 }
817 } else if (mb.button_index == BUTTON_RIGHT) {
818
819 if (mb.pressed) {
820
821 if (tool == TOOL_SELECTING || selection_active) {
822
823 tool = TOOL_NONE;
824 selection_active = false;
825
826 canvas_item_editor->update();
827
828 return true;
829 }
830
831 if (tool == TOOL_DUPLICATING) {
832
833 tool = TOOL_NONE;
834 copydata.clear();
835
836 canvas_item_editor->update();
837
838 return true;
839 }
840
841 if (tool == TOOL_NONE) {
842
843 paint_undo.clear();
844
845 Point2 local = node->world_to_map(xform_inv.xform(Point2(mb.x, mb.y)));
846
847 if (mb.mod.shift) {
848
849 if (mb.mod.control)
850 tool = TOOL_RECTANGLE_ERASE;
851 else
852 tool = TOOL_LINE_ERASE;
853
854 selection_active = false;
855 rectangle_begin = local;
856 } else {
857
858 tool = TOOL_ERASING;
859
860 paint_undo[local] = _get_op_from_cell(local);
861 _set_cell(local, TileMap::INVALID_CELL);
862 }
863
864 return true;
865 }
866
867 } else {
868 if (tool == TOOL_ERASING || tool == TOOL_RECTANGLE_ERASE || tool == TOOL_LINE_ERASE) {
869
870 if (paint_undo.size()) {
871 undo_redo->create_action(TTR("Erase TileMap"));
872 for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) {
873
874 Point2 p = E->key();
875 undo_redo->add_do_method(node, "set_cellv", p, TileMap::INVALID_CELL, false, false, false);
876 undo_redo->add_undo_method(node, "set_cellv", p, E->get().idx, E->get().xf, E->get().yf, E->get().tr);
877 }
878
879 undo_redo->commit_action();
880 paint_undo.clear();
881 }
882
883 if (tool == TOOL_RECTANGLE_ERASE || tool == TOOL_LINE_ERASE) {
884 canvas_item_editor->update();
885 }
886
887 tool = TOOL_NONE;
888
889 return true;
890
891 } else if (tool == TOOL_BUCKET) {
892
893 Dictionary pop;
894 pop["id"] = node->get_cell(over_tile.x, over_tile.y);
895 pop["flip_h"] = node->is_cell_x_flipped(over_tile.x, over_tile.y);
896 pop["flip_v"] = node->is_cell_y_flipped(over_tile.x, over_tile.y);
897 pop["transpose"] = node->is_cell_transposed(over_tile.x, over_tile.y);
898
899 DVector<Vector2> points = _bucket_fill(over_tile, true);
900
901 if (points.size() == 0)
902 return false;
903
904 undo_redo->create_action("Bucket Fill");
905
906 undo_redo->add_do_method(this, "_erase_points", points);
907 undo_redo->add_undo_method(this, "_fill_points", points, pop);
908
909 undo_redo->commit_action();
910 }
911 }
912 }
913 } break;
914 case InputEvent::MOUSE_MOTION: {
915
916 const InputEventMouseMotion &mm = p_event.mouse_motion;
917
918 Point2i new_over_tile = node->world_to_map(xform_inv.xform(Point2(mm.x, mm.y)));
919
920 if (new_over_tile != over_tile) {
921
922 over_tile = new_over_tile;
923 canvas_item_editor->update();
924 }
925
926 if (show_tile_info) {
927 int tile_under = node->get_cell(over_tile.x, over_tile.y);
928 String tile_name = "none";
929
930 if (node->get_tileset()->has_tile(tile_under))
931 tile_name = node->get_tileset()->tile_get_name(tile_under);
932 tile_info->set_text(String::num(over_tile.x) + ", " + String::num(over_tile.y) + " [" + tile_name + "]");
933 tile_info->show();
934 }
935
936 if (tool == TOOL_PAINTING) {
937
938 int id = get_selected_tile();
939 if (id != TileMap::INVALID_CELL) {
940
941 if (!paint_undo.has(over_tile)) {
942 paint_undo[over_tile] = _get_op_from_cell(over_tile);
943 }
944
945 _set_cell(over_tile, id, flip_h, flip_v, transpose);
946
947 return true;
948 }
949 }
950
951 if (tool == TOOL_SELECTING) {
952
953 _select(rectangle_begin, over_tile);
954
955 return true;
956 }
957
958 if (tool == TOOL_LINE_PAINT || tool == TOOL_LINE_ERASE) {
959
960 int id = get_selected_tile();
961 bool erasing = (tool == TOOL_LINE_ERASE);
962
963 if (erasing && paint_undo.size()) {
964
965 for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) {
966
967 _set_cell(E->key(), E->get().idx, E->get().xf, E->get().yf, E->get().tr);
968 }
969 }
970
971 paint_undo.clear();
972
973 if (id != TileMap::INVALID_CELL) {
974
975 Vector<Point2i> points = line(rectangle_begin.x, over_tile.x, rectangle_begin.y, over_tile.y);
976
977 for (int i = 0; i < points.size(); i++) {
978
979 paint_undo[points[i]] = _get_op_from_cell(points[i]);
980
981 if (erasing)
982 _set_cell(points[i], TileMap::INVALID_CELL);
983 }
984
985 canvas_item_editor->update();
986 }
987
988 return true;
989 }
990 if (tool == TOOL_RECTANGLE_PAINT || tool == TOOL_RECTANGLE_ERASE) {
991
992 _select(rectangle_begin, over_tile);
993
994 if (tool == TOOL_RECTANGLE_ERASE) {
995
996 if (paint_undo.size()) {
997
998 for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) {
999
1000 _set_cell(E->key(), E->get().idx, E->get().xf, E->get().yf, E->get().tr);
1001 }
1002 }
1003
1004 paint_undo.clear();
1005
1006 for (int i = rectangle.pos.y; i <= rectangle.pos.y + rectangle.size.y; i++) {
1007 for (int j = rectangle.pos.x; j <= rectangle.pos.x + rectangle.size.x; j++) {
1008
1009 Point2i tile = Point2i(j, i);
1010 paint_undo[tile] = _get_op_from_cell(tile);
1011
1012 _set_cell(tile, TileMap::INVALID_CELL);
1013 }
1014 }
1015 }
1016
1017 return true;
1018 }
1019 if (tool == TOOL_ERASING) {
1020
1021 if (!paint_undo.has(over_tile)) {
1022 paint_undo[over_tile] = _get_op_from_cell(over_tile);
1023 }
1024
1025 _set_cell(over_tile, TileMap::INVALID_CELL);
1026
1027 return true;
1028 }
1029 if (tool == TOOL_PICKING && Input::get_singleton()->is_mouse_button_pressed(BUTTON_LEFT)) {
1030
1031 _pick_tile(over_tile);
1032
1033 return true;
1034 }
1035 } break;
1036 case InputEvent::KEY: {
1037
1038 const InputEventKey &k = p_event.key;
1039
1040 if (!k.pressed)
1041 break;
1042
1043 if (k.scancode == KEY_ESCAPE) {
1044
1045 if (tool == TOOL_DUPLICATING)
1046 copydata.clear();
1047 else if (tool == TOOL_SELECTING || selection_active)
1048 selection_active = false;
1049
1050 tool = TOOL_NONE;
1051
1052 canvas_item_editor->update();
1053
1054 return true;
1055 }
1056
1057 if (tool != TOOL_NONE || !mouse_over)
1058 return false;
1059
1060 if (ED_IS_SHORTCUT("tile_map_editor/erase_selection", p_event)) {
1061 _menu_option(OPTION_ERASE_SELECTION);
1062
1063 return true;
1064 }
1065 if (ED_IS_SHORTCUT("tile_map_editor/select", p_event)) {
1066 tool = TOOL_SELECTING;
1067 selection_active = false;
1068
1069 canvas_item_editor->update();
1070
1071 return true;
1072 }
1073 if (ED_IS_SHORTCUT("tile_map_editor/duplicate_selection", p_event)) {
1074 _update_copydata();
1075
1076 if (selection_active) {
1077 tool = TOOL_DUPLICATING;
1078
1079 canvas_item_editor->update();
1080
1081 return true;
1082 }
1083 }
1084 if (ED_IS_SHORTCUT("tile_map_editor/find_tile", p_event)) {
1085 search_box->select_all();
1086 search_box->grab_focus();
1087
1088 return true;
1089 }
1090 if (ED_IS_SHORTCUT("tile_map_editor/mirror_x", p_event)) {
1091 flip_h = !flip_h;
1092 mirror_x->set_pressed(flip_h);
1093 canvas_item_editor->update();
1094 return true;
1095 }
1096 if (ED_IS_SHORTCUT("tile_map_editor/mirror_y", p_event)) {
1097 flip_v = !flip_v;
1098 mirror_y->set_pressed(flip_v);
1099 canvas_item_editor->update();
1100 return true;
1101 }
1102 if (ED_IS_SHORTCUT("tile_map_editor/transpose", p_event)) {
1103 transpose = !transpose;
1104 transp->set_pressed(transpose);
1105 canvas_item_editor->update();
1106 return true;
1107 }
1108 } break;
1109 }
1110
1111 return false;
1112 }
1113
_canvas_draw()1114 void TileMapEditor::_canvas_draw() {
1115
1116 if (!node)
1117 return;
1118
1119 Matrix32 cell_xf = node->get_cell_transform();
1120
1121 Matrix32 xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * node->get_global_transform();
1122 Matrix32 xform_inv = xform.affine_inverse();
1123
1124 Size2 screen_size = canvas_item_editor->get_size();
1125 {
1126 Rect2 aabb;
1127 aabb.pos = node->world_to_map(xform_inv.xform(Vector2()));
1128 aabb.expand_to(node->world_to_map(xform_inv.xform(Vector2(0, screen_size.height))));
1129 aabb.expand_to(node->world_to_map(xform_inv.xform(Vector2(screen_size.width, 0))));
1130 aabb.expand_to(node->world_to_map(xform_inv.xform(screen_size)));
1131 Rect2i si = aabb.grow(1.0);
1132
1133 if (node->get_half_offset() != TileMap::HALF_OFFSET_X) {
1134
1135 int max_lines = 2000; //avoid crash if size too smal
1136
1137 for (int i = (si.pos.x) - 1; i <= (si.pos.x + si.size.x); i++) {
1138
1139 Vector2 from = xform.xform(node->map_to_world(Vector2(i, si.pos.y)));
1140 Vector2 to = xform.xform(node->map_to_world(Vector2(i, si.pos.y + si.size.y + 1)));
1141
1142 Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2);
1143 canvas_item_editor->draw_line(from, to, col, 1);
1144 if (max_lines-- == 0)
1145 break;
1146 }
1147 } else {
1148
1149 int max_lines = 10000; //avoid crash if size too smal
1150
1151 for (int i = (si.pos.x) - 1; i <= (si.pos.x + si.size.x); i++) {
1152
1153 for (int j = (si.pos.y) - 1; j <= (si.pos.y + si.size.y); j++) {
1154
1155 Vector2 ofs;
1156 if (ABS(j) & 1) {
1157 ofs = cell_xf[0] * 0.5;
1158 }
1159
1160 Vector2 from = xform.xform(node->map_to_world(Vector2(i, j), true) + ofs);
1161 Vector2 to = xform.xform(node->map_to_world(Vector2(i, j + 1), true) + ofs);
1162 Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2);
1163 canvas_item_editor->draw_line(from, to, col, 1);
1164
1165 if (max_lines-- == 0)
1166 break;
1167 }
1168 }
1169 }
1170
1171 int max_lines = 10000; //avoid crash if size too smal
1172
1173 if (node->get_half_offset() != TileMap::HALF_OFFSET_Y) {
1174
1175 for (int i = (si.pos.y) - 1; i <= (si.pos.y + si.size.y); i++) {
1176
1177 Vector2 from = xform.xform(node->map_to_world(Vector2(si.pos.x, i)));
1178 Vector2 to = xform.xform(node->map_to_world(Vector2(si.pos.x + si.size.x + 1, i)));
1179
1180 Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2);
1181 canvas_item_editor->draw_line(from, to, col, 1);
1182
1183 if (max_lines-- == 0)
1184 break;
1185 }
1186 } else {
1187
1188 for (int i = (si.pos.y) - 1; i <= (si.pos.y + si.size.y); i++) {
1189
1190 for (int j = (si.pos.x) - 1; j <= (si.pos.x + si.size.x); j++) {
1191
1192 Vector2 ofs;
1193 if (ABS(j) & 1) {
1194 ofs = cell_xf[1] * 0.5;
1195 }
1196
1197 Vector2 from = xform.xform(node->map_to_world(Vector2(j, i), true) + ofs);
1198 Vector2 to = xform.xform(node->map_to_world(Vector2(j + 1, i), true) + ofs);
1199 Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2);
1200 canvas_item_editor->draw_line(from, to, col, 1);
1201
1202 if (max_lines-- == 0)
1203 break;
1204 }
1205 }
1206 }
1207 }
1208
1209 if (selection_active) {
1210
1211 Vector<Vector2> points;
1212 points.push_back(xform.xform(node->map_to_world((rectangle.pos))));
1213 points.push_back(xform.xform(node->map_to_world((rectangle.pos + Point2(rectangle.size.x + 1, 0)))));
1214 points.push_back(xform.xform(node->map_to_world((rectangle.pos + Point2(rectangle.size.x + 1, rectangle.size.y + 1)))));
1215 points.push_back(xform.xform(node->map_to_world((rectangle.pos + Point2(0, rectangle.size.y + 1)))));
1216
1217 canvas_item_editor->draw_colored_polygon(points, Color(0.2, 0.8, 1, 0.4));
1218 }
1219
1220 if (mouse_over) {
1221
1222 Vector2 endpoints[4] = {
1223 node->map_to_world(over_tile, true),
1224 node->map_to_world((over_tile + Point2(1, 0)), true),
1225 node->map_to_world((over_tile + Point2(1, 1)), true),
1226 node->map_to_world((over_tile + Point2(0, 1)), true)
1227 };
1228
1229 for (int i = 0; i < 4; i++) {
1230 if (node->get_half_offset() == TileMap::HALF_OFFSET_X && ABS(over_tile.y) & 1)
1231 endpoints[i] += cell_xf[0] * 0.5;
1232 if (node->get_half_offset() == TileMap::HALF_OFFSET_Y && ABS(over_tile.x) & 1)
1233 endpoints[i] += cell_xf[1] * 0.5;
1234 endpoints[i] = xform.xform(endpoints[i]);
1235 }
1236 Color col;
1237 if (node->get_cell(over_tile.x, over_tile.y) != TileMap::INVALID_CELL)
1238 col = Color(0.2, 0.8, 1.0, 0.8);
1239 else
1240 col = Color(1.0, 0.4, 0.2, 0.8);
1241
1242 for (int i = 0; i < 4; i++)
1243 canvas_item_editor->draw_line(endpoints[i], endpoints[(i + 1) % 4], col, 2);
1244
1245 bool bucket_preview = EditorSettings::get_singleton()->get("tile_map/bucket_fill_preview");
1246 if (tool == TOOL_SELECTING || tool == TOOL_PICKING || !bucket_preview) {
1247 return;
1248 }
1249
1250 if (tool == TOOL_LINE_PAINT) {
1251
1252 if (paint_undo.empty())
1253 return;
1254
1255 int id = get_selected_tile();
1256
1257 if (id == TileMap::INVALID_CELL)
1258 return;
1259
1260 for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) {
1261
1262 _draw_cell(id, E->key(), flip_h, flip_v, transpose, xform);
1263 }
1264
1265 } else if (tool == TOOL_RECTANGLE_PAINT) {
1266
1267 int id = get_selected_tile();
1268
1269 if (id == TileMap::INVALID_CELL)
1270 return;
1271
1272 for (int i = rectangle.pos.y; i <= rectangle.pos.y + rectangle.size.y; i++) {
1273 for (int j = rectangle.pos.x; j <= rectangle.pos.x + rectangle.size.x; j++) {
1274
1275 _draw_cell(id, Point2i(j, i), flip_h, flip_v, transpose, xform);
1276 }
1277 }
1278 } else if (tool == TOOL_DUPLICATING) {
1279
1280 if (copydata.empty())
1281 return;
1282
1283 Ref<TileSet> ts = node->get_tileset();
1284
1285 if (ts.is_null())
1286 return;
1287
1288 Point2 ofs = over_tile - rectangle.pos;
1289
1290 for (List<TileData>::Element *E = copydata.front(); E; E = E->next()) {
1291
1292 if (!ts->has_tile(E->get().cell))
1293 continue;
1294
1295 TileData tcd = E->get();
1296
1297 _draw_cell(tcd.cell, tcd.pos + ofs, tcd.flip_h, tcd.flip_v, tcd.transpose, xform);
1298 }
1299
1300 Rect2i duplicate = rectangle;
1301 duplicate.pos = over_tile;
1302
1303 Vector<Vector2> points;
1304 points.push_back(xform.xform(node->map_to_world(duplicate.pos)));
1305 points.push_back(xform.xform(node->map_to_world((duplicate.pos + Point2(duplicate.size.x + 1, 0)))));
1306 points.push_back(xform.xform(node->map_to_world((duplicate.pos + Point2(duplicate.size.x + 1, duplicate.size.y + 1)))));
1307 points.push_back(xform.xform(node->map_to_world((duplicate.pos + Point2(0, duplicate.size.y + 1)))));
1308
1309 canvas_item_editor->draw_colored_polygon(points, Color(0.2, 1.0, 0.8, 0.2));
1310
1311 } else if (tool == TOOL_BUCKET) {
1312
1313 int tile = get_selected_tile();
1314 _draw_fill_preview(tile, over_tile, flip_h, flip_v, transpose, xform);
1315
1316 } else {
1317
1318 int st = get_selected_tile();
1319
1320 if (st == TileMap::INVALID_CELL)
1321 return;
1322
1323 _draw_cell(st, over_tile, flip_h, flip_v, transpose, xform);
1324 }
1325 }
1326 }
1327
edit(Node * p_tile_map)1328 void TileMapEditor::edit(Node *p_tile_map) {
1329
1330 search_box->set_text("");
1331
1332 if (!canvas_item_editor) {
1333 canvas_item_editor = CanvasItemEditor::get_singleton()->get_viewport_control();
1334 }
1335
1336 if (node)
1337 node->disconnect("settings_changed", this, "_tileset_settings_changed");
1338 if (p_tile_map) {
1339
1340 node = p_tile_map->cast_to<TileMap>();
1341 if (!canvas_item_editor->is_connected("draw", this, "_canvas_draw"))
1342 canvas_item_editor->connect("draw", this, "_canvas_draw");
1343 if (!canvas_item_editor->is_connected("mouse_enter", this, "_canvas_mouse_enter"))
1344 canvas_item_editor->connect("mouse_enter", this, "_canvas_mouse_enter");
1345 if (!canvas_item_editor->is_connected("mouse_exit", this, "_canvas_mouse_exit"))
1346 canvas_item_editor->connect("mouse_exit", this, "_canvas_mouse_exit");
1347
1348 _update_palette();
1349
1350 } else {
1351 node = NULL;
1352
1353 if (canvas_item_editor->is_connected("draw", this, "_canvas_draw"))
1354 canvas_item_editor->disconnect("draw", this, "_canvas_draw");
1355 if (canvas_item_editor->is_connected("mouse_enter", this, "_canvas_mouse_enter"))
1356 canvas_item_editor->disconnect("mouse_enter", this, "_canvas_mouse_enter");
1357 if (canvas_item_editor->is_connected("mouse_exit", this, "_canvas_mouse_exit"))
1358 canvas_item_editor->disconnect("mouse_exit", this, "_canvas_mouse_exit");
1359
1360 _update_palette();
1361 }
1362
1363 if (node)
1364 node->connect("settings_changed", this, "_tileset_settings_changed");
1365
1366 _clear_bucket_cache();
1367 }
1368
_tileset_settings_changed()1369 void TileMapEditor::_tileset_settings_changed() {
1370
1371 _update_palette();
1372
1373 if (canvas_item_editor)
1374 canvas_item_editor->update();
1375 }
1376
_icon_size_changed(float p_value)1377 void TileMapEditor::_icon_size_changed(float p_value) {
1378 if (node) {
1379 palette->set_icon_scale(p_value);
1380 _update_palette();
1381 }
1382 }
1383
_bind_methods()1384 void TileMapEditor::_bind_methods() {
1385
1386 ObjectTypeDB::bind_method(_MD("_text_entered"), &TileMapEditor::_text_entered);
1387 ObjectTypeDB::bind_method(_MD("_text_changed"), &TileMapEditor::_text_changed);
1388 ObjectTypeDB::bind_method(_MD("_sbox_input"), &TileMapEditor::_sbox_input);
1389 ObjectTypeDB::bind_method(_MD("_menu_option"), &TileMapEditor::_menu_option);
1390 ObjectTypeDB::bind_method(_MD("_canvas_draw"), &TileMapEditor::_canvas_draw);
1391 ObjectTypeDB::bind_method(_MD("_canvas_mouse_enter"), &TileMapEditor::_canvas_mouse_enter);
1392 ObjectTypeDB::bind_method(_MD("_canvas_mouse_exit"), &TileMapEditor::_canvas_mouse_exit);
1393 ObjectTypeDB::bind_method(_MD("_tileset_settings_changed"), &TileMapEditor::_tileset_settings_changed);
1394 ObjectTypeDB::bind_method(_MD("_update_transform_buttons"), &TileMapEditor::_update_transform_buttons);
1395
1396 ObjectTypeDB::bind_method(_MD("_fill_points"), &TileMapEditor::_fill_points);
1397 ObjectTypeDB::bind_method(_MD("_erase_points"), &TileMapEditor::_erase_points);
1398
1399 ObjectTypeDB::bind_method(_MD("_icon_size_changed"), &TileMapEditor::_icon_size_changed);
1400 }
1401
_get_op_from_cell(const Point2i & p_pos)1402 TileMapEditor::CellOp TileMapEditor::_get_op_from_cell(const Point2i &p_pos) {
1403 CellOp op;
1404 op.idx = node->get_cell(p_pos.x, p_pos.y);
1405 if (op.idx != TileMap::INVALID_CELL) {
1406 if (node->is_cell_x_flipped(p_pos.x, p_pos.y))
1407 op.xf = true;
1408 if (node->is_cell_y_flipped(p_pos.x, p_pos.y))
1409 op.yf = true;
1410 if (node->is_cell_transposed(p_pos.x, p_pos.y))
1411 op.tr = true;
1412 }
1413 return op;
1414 }
1415
_update_transform_buttons(Object * p_button)1416 void TileMapEditor::_update_transform_buttons(Object *p_button) {
1417 //ERR_FAIL_NULL(p_button);
1418 ToolButton *b = p_button->cast_to<ToolButton>();
1419 //ERR_FAIL_COND(!b);
1420
1421 if (b == rotate_0) {
1422 mirror_x->set_pressed(false);
1423 mirror_y->set_pressed(false);
1424 transp->set_pressed(false);
1425 } else if (b == rotate_90) {
1426 mirror_x->set_pressed(true);
1427 mirror_y->set_pressed(false);
1428 transp->set_pressed(true);
1429 } else if (b == rotate_180) {
1430 mirror_x->set_pressed(true);
1431 mirror_y->set_pressed(true);
1432 transp->set_pressed(false);
1433 } else if (b == rotate_270) {
1434 mirror_x->set_pressed(false);
1435 mirror_y->set_pressed(true);
1436 transp->set_pressed(true);
1437 }
1438
1439 flip_h = mirror_x->is_pressed();
1440 flip_v = mirror_y->is_pressed();
1441 transpose = transp->is_pressed();
1442
1443 rotate_0->set_pressed(!flip_h && !flip_v && !transpose);
1444 rotate_90->set_pressed(flip_h && !flip_v && transpose);
1445 rotate_180->set_pressed(flip_h && flip_v && !transpose);
1446 rotate_270->set_pressed(!flip_h && flip_v && transpose);
1447 }
1448
TileMapEditor(EditorNode * p_editor)1449 TileMapEditor::TileMapEditor(EditorNode *p_editor) {
1450
1451 node = NULL;
1452 canvas_item_editor = NULL;
1453 editor = p_editor;
1454 undo_redo = editor->get_undo_redo();
1455
1456 tool = TOOL_NONE;
1457 selection_active = false;
1458 mouse_over = false;
1459 show_tile_info = true;
1460
1461 flip_h = false;
1462 flip_v = false;
1463 transpose = false;
1464
1465 bucket_cache_tile = -1;
1466 bucket_cache_visited = 0;
1467
1468 ED_SHORTCUT("tile_map_editor/erase_selection", TTR("Erase selection"), KEY_DELETE);
1469 ED_SHORTCUT("tile_map_editor/find_tile", TTR("Find tile"), KEY_MASK_CMD + KEY_F);
1470 ED_SHORTCUT("tile_map_editor/transpose", TTR("Transpose"));
1471 ED_SHORTCUT("tile_map_editor/mirror_x", TTR("Mirror X"), KEY_A);
1472 ED_SHORTCUT("tile_map_editor/mirror_y", TTR("Mirror Y"), KEY_S);
1473
1474 HBoxContainer *tool_hb1 = memnew(HBoxContainer);
1475 add_child(tool_hb1);
1476 HBoxContainer *tool_hb2 = memnew(HBoxContainer);
1477 add_child(tool_hb2);
1478
1479 search_box = memnew(LineEdit);
1480 search_box->set_h_size_flags(SIZE_EXPAND_FILL);
1481 search_box->connect("text_entered", this, "_text_entered");
1482 search_box->connect("text_changed", this, "_text_changed");
1483 search_box->connect("input_event", this, "_sbox_input");
1484 add_child(search_box);
1485
1486 size_slider = memnew(HSlider);
1487 size_slider->set_h_size_flags(SIZE_EXPAND_FILL);
1488 size_slider->set_min(0.1f);
1489 size_slider->set_max(4.0f);
1490 size_slider->set_step(0.1f);
1491 size_slider->set_val(1.0f);
1492 size_slider->connect("value_changed", this, "_icon_size_changed");
1493 add_child(size_slider);
1494
1495 int mw = EDITOR_DEF("tile_map/palette_min_width", 80);
1496
1497 // Add tile palette
1498 palette = memnew(ItemList);
1499 palette->set_v_size_flags(SIZE_EXPAND_FILL);
1500 palette->set_custom_minimum_size(Size2(mw, 0));
1501 palette->set_max_columns(0);
1502 palette->set_icon_mode(ItemList::ICON_MODE_TOP);
1503 palette->set_max_text_lines(2);
1504 add_child(palette);
1505
1506 // Add menu items
1507 toolbar = memnew(HBoxContainer);
1508 toolbar->set_h_size_flags(SIZE_EXPAND_FILL);
1509 toolbar->set_alignment(BoxContainer::ALIGN_END);
1510 toolbar->hide();
1511 CanvasItemEditor::get_singleton()->add_control_to_menu_panel(toolbar);
1512
1513 // Tile position
1514 tile_info = memnew(Label);
1515 toolbar->add_child(tile_info);
1516
1517 options = memnew(MenuButton);
1518 options->set_text("Tile Map");
1519 options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("TileMap", "EditorIcons"));
1520 options->set_process_unhandled_key_input(false);
1521
1522 PopupMenu *p = options->get_popup();
1523
1524 p->add_item(TTR("Bucket"), OPTION_BUCKET);
1525 p->add_separator();
1526 p->add_item(TTR("Pick Tile"), OPTION_PICK_TILE, KEY_CONTROL);
1527 p->add_separator();
1528 p->add_shortcut(ED_SHORTCUT("tile_map_editor/select", TTR("Select"), KEY_MASK_CMD + KEY_B), OPTION_SELECT);
1529 p->add_shortcut(ED_SHORTCUT("tile_map_editor/duplicate_selection", TTR("Duplicate Selection"), KEY_MASK_CMD + KEY_D), OPTION_DUPLICATE);
1530 p->add_shortcut(ED_GET_SHORTCUT("tile_map_editor/erase_selection"), OPTION_ERASE_SELECTION);
1531
1532 p->connect("item_pressed", this, "_menu_option");
1533
1534 toolbar->add_child(options);
1535
1536 transp = memnew(ToolButton);
1537 transp->set_toggle_mode(true);
1538 transp->set_tooltip(TTR("Transpose") + " (" + ED_GET_SHORTCUT("tile_map_editor/transpose")->get_as_text() + ")");
1539 transp->set_focus_mode(FOCUS_NONE);
1540 transp->connect("pressed", this, "_update_transform_buttons", make_binds(transp));
1541 tool_hb1->add_child(transp);
1542 mirror_x = memnew(ToolButton);
1543 mirror_x->set_toggle_mode(true);
1544 mirror_x->set_tooltip(TTR("Mirror X") + " (" + ED_GET_SHORTCUT("tile_map_editor/mirror_x")->get_as_text() + ")");
1545 mirror_x->set_focus_mode(FOCUS_NONE);
1546 mirror_x->connect("pressed", this, "_update_transform_buttons", make_binds(mirror_x));
1547 tool_hb1->add_child(mirror_x);
1548 mirror_y = memnew(ToolButton);
1549 mirror_y->set_toggle_mode(true);
1550 mirror_y->set_tooltip(TTR("Mirror Y") + " (" + ED_GET_SHORTCUT("tile_map_editor/mirror_y")->get_as_text() + ")");
1551 mirror_y->set_focus_mode(FOCUS_NONE);
1552 mirror_y->connect("pressed", this, "_update_transform_buttons", make_binds(mirror_y));
1553 tool_hb1->add_child(mirror_y);
1554
1555 rotate_0 = memnew(ToolButton);
1556 rotate_0->set_toggle_mode(true);
1557 rotate_0->set_tooltip(TTR("Rotate 0 degrees"));
1558 rotate_0->set_focus_mode(FOCUS_NONE);
1559 rotate_0->connect("pressed", this, "_update_transform_buttons", make_binds(rotate_0));
1560 tool_hb2->add_child(rotate_0);
1561 rotate_90 = memnew(ToolButton);
1562 rotate_90->set_toggle_mode(true);
1563 rotate_90->set_tooltip(TTR("Rotate 90 degrees"));
1564 rotate_90->set_focus_mode(FOCUS_NONE);
1565 rotate_90->connect("pressed", this, "_update_transform_buttons", make_binds(rotate_90));
1566 tool_hb2->add_child(rotate_90);
1567 rotate_180 = memnew(ToolButton);
1568 rotate_180->set_toggle_mode(true);
1569 rotate_180->set_tooltip(TTR("Rotate 180 degrees"));
1570 rotate_180->set_focus_mode(FOCUS_NONE);
1571 rotate_180->connect("pressed", this, "_update_transform_buttons", make_binds(rotate_180));
1572 tool_hb2->add_child(rotate_180);
1573 rotate_270 = memnew(ToolButton);
1574 rotate_270->set_toggle_mode(true);
1575 rotate_270->set_tooltip(TTR("Rotate 270 degrees"));
1576 rotate_270->set_focus_mode(FOCUS_NONE);
1577 rotate_270->connect("pressed", this, "_update_transform_buttons", make_binds(rotate_270));
1578 tool_hb2->add_child(rotate_270);
1579
1580 rotate_0->set_pressed(true);
1581 }
1582
~TileMapEditor()1583 TileMapEditor::~TileMapEditor() {
1584 _clear_bucket_cache();
1585 }
1586
1587 ///////////////////////////////////////////////////////////////
1588 ///////////////////////////////////////////////////////////////
1589 ///////////////////////////////////////////////////////////////
1590
edit(Object * p_object)1591 void TileMapEditorPlugin::edit(Object *p_object) {
1592
1593 tile_map_editor->edit(p_object->cast_to<Node>());
1594 }
1595
handles(Object * p_object) const1596 bool TileMapEditorPlugin::handles(Object *p_object) const {
1597
1598 return p_object->is_type("TileMap");
1599 }
1600
make_visible(bool p_visible)1601 void TileMapEditorPlugin::make_visible(bool p_visible) {
1602
1603 if (p_visible) {
1604
1605 tile_map_editor->show();
1606 tile_map_editor->get_toolbar()->show();
1607 } else {
1608
1609 tile_map_editor->hide();
1610 tile_map_editor->get_toolbar()->hide();
1611 tile_map_editor->edit(NULL);
1612 }
1613 }
1614
TileMapEditorPlugin(EditorNode * p_node)1615 TileMapEditorPlugin::TileMapEditorPlugin(EditorNode *p_node) {
1616
1617 EDITOR_DEF("tile_map/preview_size", 64);
1618 EDITOR_DEF("tile_map/palette_item_hseparation", 8);
1619 EDITOR_DEF("tile_map/show_tile_names", true);
1620 EDITOR_DEF("tile_map/show_tile_ids", true);
1621 EDITOR_DEF("tile_map/sort_tiles_by_name", false);
1622 EDITOR_DEF("tile_map/bucket_fill_preview", true);
1623 EDITOR_DEF("tile_map/show_tile_info_on_hover", true);
1624
1625 tile_map_editor = memnew(TileMapEditor(p_node));
1626 add_control_to_container(CONTAINER_CANVAS_EDITOR_SIDE, tile_map_editor);
1627 tile_map_editor->hide();
1628 }
1629
~TileMapEditorPlugin()1630 TileMapEditorPlugin::~TileMapEditorPlugin() {
1631 }
1632