1// SuperTux
2// Copyright (C) 2015 Hume2 <teratux.mail@gmail.com>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17#include "editor/overlay_widget.hpp"
18
19#include "util/reader_document.hpp"
20#include "util/reader_mapping.hpp"
21#include "util/writer.hpp"
22
23#include "editor/editor.hpp"
24#include "editor/node_marker.hpp"
25#include "editor/object_menu.hpp"
26#include "editor/object_info.hpp"
27#include "editor/tile_selection.hpp"
28#include "editor/tip.hpp"
29#include "editor/util.hpp"
30#include "editor/worldmap_objects.hpp"
31#include "gui/menu.hpp"
32#include "gui/menu_manager.hpp"
33#include "object/camera.hpp"
34#include "object/path_gameobject.hpp"
35#include "object/tilemap.hpp"
36#include "supertux/game_object_factory.hpp"
37#include "supertux/sector.hpp"
38#include "video/renderer.hpp"
39#include "video/video_system.hpp"
40#include "video/viewport.hpp"
41
42namespace {
43
44 const int snap_grid_sizes[4] = {4, 8, 16, 32};
45
46} // namespace
47
48bool EditorOverlayWidget::render_background = true;
49bool EditorOverlayWidget::render_grid = true;
50bool EditorOverlayWidget::snap_to_grid = true;
51int EditorOverlayWidget::selected_snap_grid_size = 3;
52
53EditorOverlayWidget::EditorOverlayWidget(Editor& editor) :
54 m_editor(editor),
55 m_hovered_tile(0, 0),
56 m_sector_pos(0, 0),
57 m_mouse_pos(0, 0),
58 m_dragging(false),
59 m_dragging_right(false),
60 m_drag_start(0, 0),
61 m_dragged_object(nullptr),
62 m_hovered_object(nullptr),
63 m_selected_object(nullptr),
64 m_edited_path(nullptr),
65 m_last_node_marker(nullptr),
66 m_object_tip(),
67 m_obj_mouse_desync(0, 0)
68{
69}
70
71EditorOverlayWidget::~EditorOverlayWidget()
72{
73}
74
75void
76EditorOverlayWidget::update(float dt_sec)
77{
78 if (m_hovered_object && !m_hovered_object->is_valid()) {
79 m_hovered_object = nullptr;
80 m_object_tip = nullptr;
81 }
82
83 if (m_selected_object && !m_selected_object->is_valid()) {
84 delete_markers();
85 }
86}
87
88void
89EditorOverlayWidget::on_level_change()
90{
91 m_dragged_object = nullptr;
92 m_selected_object = nullptr;
93 m_edited_path = nullptr;
94 m_last_node_marker = nullptr;
95 m_hovered_object = nullptr;
96}
97
98void
99EditorOverlayWidget::delete_markers()
100{
101 auto* sector = m_editor.get_sector();
102
103 if (m_selected_object) {
104 m_selected_object->editor_deselect();
105 }
106
107 for (auto& marker : sector->get_objects_by_type<MarkerObject>()) {
108 marker.remove_me();
109 }
110
111 m_selected_object = nullptr;
112 m_edited_path = nullptr;
113 m_last_node_marker = nullptr;
114}
115
116Rectf
117EditorOverlayWidget::drag_rect()
118{
119 int start_x, start_y, end_x, end_y;
120
121 if (m_drag_start.x < m_sector_pos.x) {
122 start_x = static_cast<int>(m_drag_start.x);
123 end_x = static_cast<int>(m_sector_pos.x);
124 } else {
125 start_x = static_cast<int>(m_sector_pos.x);
126 end_x = static_cast<int>(m_drag_start.x);
127 }
128
129 if (m_drag_start.y < m_sector_pos.y) {
130 start_y = static_cast<int>(m_drag_start.y);
131 end_y = static_cast<int>(m_sector_pos.y);
132 } else {
133 start_y = static_cast<int>(m_sector_pos.y);
134 end_y = static_cast<int>(m_drag_start.y);
135 }
136
137 return Rectf( static_cast<float>(start_x),
138 static_cast<float>(start_y),
139 static_cast<float>(end_x),
140 static_cast<float>(end_y) );
141}
142
143void
144EditorOverlayWidget::input_tile(const Vector& pos, uint32_t tile)
145{
146 auto tilemap = dynamic_cast<TileMap*>(m_editor.get_selected_tilemap());
147 if ( !tilemap ) {
148 return;
149 }
150
151 if ( pos.x < 0 ||
152 pos.y < 0 ||
153 pos.x >= static_cast<float>(tilemap->get_width()) ||
154 pos.y >= static_cast<float>(tilemap->get_height())) {
155 return;
156 }
157
158 tilemap->change(static_cast<int>(pos.x), static_cast<int>(pos.y), tile);
159}
160
161void
162EditorOverlayWidget::put_tile()
163{
164 auto tiles = m_editor.get_tiles();
165 Vector add_tile;
166 for (add_tile.x = static_cast<float>(tiles->m_width) - 1.0f; add_tile.x >= 0.0f; add_tile.x--) {
167 for (add_tile.y = static_cast<float>(tiles->m_height) - 1.0f; add_tile.y >= 0; add_tile.y--) {
168 input_tile(m_hovered_tile + add_tile, tiles->pos(static_cast<int>(add_tile.x),
169 static_cast<int>(add_tile.y)));
170 }
171 }
172}
173
174void
175EditorOverlayWidget::draw_rectangle()
176{
177 Rectf dr = drag_rect();
178 dr.set_p1(sp_to_tp(dr.p1()));
179 dr.set_p2(sp_to_tp(dr.p2()));
180 bool sgn_x = m_drag_start.x < m_sector_pos.x;
181 bool sgn_y = m_drag_start.y < m_sector_pos.y;
182
183 int x_ = sgn_x ? 0 : static_cast<int>(-dr.get_width());
184 for (int x = static_cast<int>(dr.get_left()); x <= static_cast<int>(dr.get_right()); x++, x_++) {
185 int y_ = sgn_y ? 0 : static_cast<int>(-dr.get_height());
186 for (int y = static_cast<int>(dr.get_top()); y <= static_cast<int>(dr.get_bottom()); y++, y_++) {
187 input_tile( Vector(static_cast<float>(x), static_cast<float>(y)), m_editor.get_tiles()->pos(x_, y_) );
188 }
189 }
190}
191
192void
193EditorOverlayWidget::fill()
194{
195 auto tiles = m_editor.get_tiles();
196 auto tilemap = dynamic_cast<TileMap*>(m_editor.get_selected_tilemap());
197 if (! tilemap) {
198 return;
199 }
200
201 // The tile that is going to be replaced:
202 Uint32 replace_tile = tilemap->get_tile_id(static_cast<int>(m_hovered_tile.x), static_cast<int>(m_hovered_tile.y));
203
204 if (replace_tile == tiles->pos(0, 0)) {
205 // Replacing by the same tiles shouldn't do anything.
206 return;
207 }
208
209 std::vector<Vector> pos_stack;
210 pos_stack.clear();
211 pos_stack.push_back(m_hovered_tile);
212
213 // Passing recursively trough all tiles to be replaced...
214 while (pos_stack.size()) {
215
216 if (pos_stack.size() > 1000000) {
217 log_warning << "More than 1'000'000 tiles in stack to fill, STOP" << std::endl;
218 return;
219 }
220
221 Vector pos = pos_stack[pos_stack.size() - 1];
222 Vector tpos = pos - m_hovered_tile;
223
224 // Tests for being inside tilemap:
225 if ( pos.x < 0 ||
226 pos.y < 0 ||
227 pos.x >= static_cast<float>(tilemap->get_width()) ||
228 pos.y >= static_cast<float>(tilemap->get_height()))
229 {
230 pos_stack.pop_back();
231 continue;
232 }
233
234 input_tile(pos, tiles->pos(static_cast<int>(tpos.x), static_cast<int>(tpos.y)));
235 Vector pos_;
236
237 // Going left...
238 pos_ = pos + Vector(-1, 0);
239 if (pos_.x >= 0) {
240 if (replace_tile == tilemap->get_tile_id(static_cast<int>(pos_.x), static_cast<int>(pos_.y)) &&
241 replace_tile != tiles->pos(static_cast<int>(tpos.x - 1), static_cast<int>(tpos.y))) {
242 pos_stack.push_back( pos_ );
243 continue;
244 }
245 }
246
247 // Going right...
248 pos_ = pos + Vector(1, 0);
249 if (pos_.x < static_cast<float>(tilemap->get_width())) {
250 if (replace_tile == tilemap->get_tile_id(static_cast<int>(pos_.x), static_cast<int>(pos_.y)) &&
251 replace_tile != tiles->pos(static_cast<int>(tpos.x + 1), static_cast<int>(tpos.y))) {
252 pos_stack.push_back( pos_ );
253 continue;
254 }
255 }
256
257 // Going up...
258 pos_ = pos + Vector(0, -1);
259 if (pos_.y >= 0) {
260 if (replace_tile == tilemap->get_tile_id(static_cast<int>(pos_.x), static_cast<int>(pos_.y))&&
261 replace_tile != tiles->pos(static_cast<int>(tpos.x), static_cast<int>(tpos.y - 1))) {
262 pos_stack.push_back( pos_ );
263 continue;
264 }
265 }
266
267 // Going down...
268 pos_ = pos + Vector(0, 1);
269 if (pos_.y < static_cast<float>(tilemap->get_height())) {
270 if (replace_tile == tilemap->get_tile_id(static_cast<int>(pos_.x), static_cast<int>(pos_.y)) &&
271 replace_tile != tiles->pos(static_cast<int>(tpos.x), static_cast<int>(tpos.y + 1))) {
272 pos_stack.push_back( pos_ );
273 continue;
274 }
275 }
276
277 // When tiles on each side are already filled or occupied by another tiles, it ends.
278 pos_stack.pop_back();
279 }
280}
281
282void
283EditorOverlayWidget::hover_object()
284{
285 for (auto& moving_object : m_editor.get_sector()->get_objects_by_type<MovingObject>())
286 {
287 Rectf bbox = moving_object.get_bbox();
288 if (bbox.contains(m_sector_pos)) {
289 if (&moving_object != m_hovered_object) {
290 m_hovered_object = &moving_object;
291 if (moving_object.has_settings()) {
292 m_object_tip = std::make_unique<Tip>(moving_object);
293 }
294 }
295 return;
296 }
297 }
298 m_object_tip = nullptr;
299 m_hovered_object = nullptr;
300}
301
302void
303EditorOverlayWidget::edit_path(Path* path, GameObject* new_marked_object)
304{
305 if (!path) return;
306 delete_markers();
307
308 if (!path->is_valid()) {
309 m_edited_path = nullptr;
310 return;
311 }
312 m_edited_path = path;
313 m_edited_path->edit_path();
314 if (new_marked_object) {
315 m_selected_object = new_marked_object;
316 }
317}
318
319void
320EditorOverlayWidget::select_object()
321{
322 delete_markers();
323 if (!m_dragged_object || !m_dragged_object->is_valid()) return;
324
325 if (m_dragged_object->has_variable_size()) {
326 m_selected_object = m_dragged_object;
327 m_dragged_object->editor_select();
328 return;
329 }
330
331 auto path_obj = dynamic_cast<PathObject*>(m_dragged_object);
332 if (path_obj && path_obj->get_path())
333 {
334 edit_path(path_obj->get_path(), m_dragged_object);
335 }
336}
337
338void
339EditorOverlayWidget::grab_object()
340{
341 if (m_hovered_object) {
342 if (!m_hovered_object->is_valid())
343 {
344 m_hovered_object = nullptr;
345 }
346 else
347 {
348 m_dragged_object = m_hovered_object;
349 m_obj_mouse_desync = m_sector_pos - m_hovered_object->get_pos();
350
351 auto* pm = dynamic_cast<MarkerObject*>(m_hovered_object);
352 if (!pm) {
353 select_object();
354 }
355 m_last_node_marker = dynamic_cast<NodeMarker*>(pm);
356 }
357 }
358 else
359 {
360 m_dragged_object = nullptr;
361
362 if (m_edited_path &&
363 m_editor.get_tileselect_object() == "#node" &&
364 m_edited_path->is_valid())
365 {
366 // do nothing
367 } else {
368 delete_markers();
369 }
370 }
371}
372
373void
374EditorOverlayWidget::clone_object()
375{
376 if (m_hovered_object && m_hovered_object->is_saveable()) {
377 if (!m_hovered_object->is_valid()) {
378 m_hovered_object = nullptr;
379 return;
380 }
381
382 auto* pm = dynamic_cast<MarkerObject*>(m_hovered_object);
383 if (!pm)
384 {
385 m_obj_mouse_desync = m_sector_pos - m_hovered_object->get_pos();
386
387 // clone the current object by means of saving and loading it
388 auto game_object_uptr = [this]{
389 std::stringstream stream;
390 Writer writer(stream);
391 writer.start_list(m_hovered_object->get_class());
392 m_hovered_object->save(writer);
393 writer.end_list(m_hovered_object->get_class());
394
395 auto doc = ReaderDocument::from_stream(stream);
396 auto object_sx = doc.get_root();
397 return GameObjectFactory::instance().create(object_sx.get_name(), object_sx.get_mapping());
398 }();
399
400 GameObject& game_object = m_editor.get_sector()->add_object(std::move(game_object_uptr));
401
402 m_dragged_object = dynamic_cast<MovingObject*>(&game_object);
403 m_dragged_object->after_editor_set();
404 }
405 }
406 else
407 {
408 m_dragged_object = nullptr;
409 delete_markers();
410 }
411}
412
413void
414EditorOverlayWidget::show_object_menu(GameObject& object)
415{
416 auto menu = std::make_unique<ObjectMenu>(m_editor, &object);
417 m_editor.m_deactivate_request = true;
418 MenuManager::instance().push_menu(std::move(menu));
419}
420
421void
422EditorOverlayWidget::move_object()
423{
424 if (m_dragged_object) {
425 if (!m_dragged_object->is_valid()) {
426 m_dragged_object = nullptr;
427 return;
428 }
429 Vector new_pos = m_sector_pos - m_obj_mouse_desync;
430 if (snap_to_grid) {
431 auto& snap_grid_size = snap_grid_sizes[selected_snap_grid_size];
432 new_pos = (new_pos / static_cast<float>(snap_grid_size)).floor() * static_cast<float>(snap_grid_size);
433
434 auto pm = dynamic_cast<MarkerObject*>(m_dragged_object);
435 if (pm) {
436 new_pos -= pm->get_offset();
437 }
438 }
439 m_dragged_object->move_to(new_pos);
440 }
441}
442
443void
444EditorOverlayWidget::rubber_object()
445{
446 if (!m_edited_path) {
447 delete_markers();
448 }
449 if (m_dragged_object) {
450 m_dragged_object->editor_delete();
451 }
452 m_last_node_marker = nullptr;
453}
454
455void
456EditorOverlayWidget::rubber_rect()
457{
458 delete_markers();
459 Rectf dr = drag_rect();
460 for (auto& moving_object : m_editor.get_sector()->get_objects_by_type<MovingObject>()) {
461 Rectf bbox = moving_object.get_bbox();
462 if (dr.contains(bbox)) {
463 moving_object.editor_delete();
464 }
465 }
466 m_last_node_marker = nullptr;
467}
468
469void
470EditorOverlayWidget::update_node_iterators()
471{
472 if (!m_edited_path) return;
473 if (!m_edited_path->is_valid()) return;
474
475 auto* sector = m_editor.get_sector();
476 for (auto& moving_object : sector->get_objects_by_type<MovingObject>()) {
477 auto marker = dynamic_cast<NodeMarker*>(&moving_object);
478 if (marker) {
479 marker->update_iterator();
480 }
481 }
482}
483
484void
485EditorOverlayWidget::add_path_node()
486{
487 Path::Node new_node;
488 new_node.position = m_sector_pos;
489 new_node.time = 1;
490 m_edited_path->m_nodes.insert(m_last_node_marker->m_node + 1, new_node);
491 Sector::get().add<NodeMarker>(m_edited_path, m_edited_path->m_nodes.end() - 1, m_edited_path->m_nodes.size() - 1);
492 //last_node_marker = dynamic_cast<NodeMarker*>(marker.get());
493 update_node_iterators();
494 m_editor.get_sector()->flush_game_objects();
495 grab_object();
496}
497
498void
499EditorOverlayWidget::put_object()
500{
501 const std::string& object_class = m_editor.get_tileselect_object();
502 if (object_class[0] == '#')
503 {
504 if (m_edited_path && object_class == "#node") {
505 if (m_edited_path->is_valid() && m_last_node_marker) {
506 add_path_node();
507 }
508 }
509 }
510 else
511 {
512 auto target_pos = m_sector_pos;
513 if (snap_to_grid)
514 {
515 auto& snap_grid_size = snap_grid_sizes[selected_snap_grid_size];
516 target_pos = (m_sector_pos / static_cast<float>(snap_grid_size)).floor() * static_cast<float>(snap_grid_size);
517 }
518
519 auto object = GameObjectFactory::instance().create(object_class, target_pos, Direction::LEFT);
520 object->after_editor_set();
521
522 auto* mo = dynamic_cast<MovingObject*> (object.get());
523 if (!mo) {
524 if (!dynamic_cast<PathGameObject*>(object.get())) {
525 m_editor.add_layer(object.get());
526 }
527 } else if (!snap_to_grid) {
528 auto bbox = mo->get_bbox();
529 mo->move_to(mo->get_pos() - Vector(bbox.get_width() / 2, bbox.get_height() / 2));
530 }
531
532 auto* wo = dynamic_cast<worldmap_editor::WorldmapObject*>(object.get());
533 if (wo) {
534 wo->move_to(wo->get_pos() / 32);
535 }
536
537 m_editor.get_sector()->add_object(std::move(object));
538 }
539}
540
541void
542EditorOverlayWidget::process_left_click()
543{
544 m_dragging = true;
545 m_dragging_right = false;
546 m_drag_start = m_sector_pos;
547
548 switch (m_editor.get_tileselect_input_type())
549 {
550 case EditorToolboxWidget::InputType::TILE:
551 switch (m_editor.get_tileselect_select_mode())
552 {
553 case 0:
554 put_tile();
555 break;
556
557 case 1:
558 draw_rectangle();
559 break;
560
561 case 2:
562 fill();
563 break;
564
565 default:
566 break;
567 }
568 break;
569
570 case EditorToolboxWidget::InputType::OBJECT:
571 switch (m_editor.get_tileselect_move_mode())
572 {
573 case 0:
574 grab_object();
575 break;
576
577 case 1:
578 clone_object();
579 break;
580
581 default:
582 break;
583 }
584
585 if (!m_editor.get_tileselect_object().empty()) {
586 if (!m_dragged_object) {
587 put_object();
588 }
589 } else {
590 rubber_object();
591 }
592 break;
593
594 default:
595 break;
596 }
597}
598
599void
600EditorOverlayWidget::process_right_click()
601{
602 switch (m_editor.get_tileselect_input_type())
603 {
604 case EditorToolboxWidget::InputType::TILE:
605 m_dragging = true;
606 m_dragging_right = true;
607 m_drag_start = m_sector_pos;
608 update_tile_selection();
609 break;
610
611 case EditorToolboxWidget::InputType::NONE:
612 case EditorToolboxWidget::InputType::OBJECT:
613 {
614 if (m_hovered_object &&
615 m_hovered_object->is_valid() &&
616 m_hovered_object->has_settings())
617 {
618 show_object_menu(*m_hovered_object);
619 }
620 }
621 break;
622
623 default:
624 break;
625 }
626}
627
628Rectf
629EditorOverlayWidget::tile_drag_rect()
630{
631 Rectf result = drag_rect();
632
633 // Increase drag rectangle size to the
634 // nearest tile border respectively.
635 result = Rectf(floorf(result.get_left() / 32) * 32,
636 floorf(result.get_top() / 32) * 32,
637 ceilf(result.get_right() / 32) * 32,
638 ceilf(result.get_bottom() / 32) * 32);
639 result.set_p1(sp_to_tp(result.p1()));
640 result.set_p2(sp_to_tp(result.p2()));
641 return result;
642}
643
644Rectf
645EditorOverlayWidget::selection_draw_rect()
646{
647 Rectf select = tile_drag_rect();
648 select.set_p1(tile_screen_pos(select.p1()));
649 select.set_p2(tile_screen_pos(select.p2()));
650 return select;
651}
652
653void
654EditorOverlayWidget::update_tile_selection()
655{
656 Rectf select = tile_drag_rect();
657 auto tiles = m_editor.get_tiles();
658 auto tilemap = dynamic_cast<TileMap*>(m_editor.get_selected_tilemap());
659 if ( !tilemap ) {
660 return;
661 }
662
663 tiles->m_tiles.clear();
664 tiles->m_width = static_cast<int>(select.get_width());
665 tiles->m_height = static_cast<int>(select.get_height());
666
667 int w = static_cast<int>(tilemap->get_width());
668 int h = static_cast<int>(tilemap->get_height());
669 for (int y = static_cast<int>(select.get_top()); y < static_cast<int>(select.get_bottom()); y++) {
670 for (int x = static_cast<int>(select.get_left()); x < static_cast<int>(select.get_right()); x++) {
671 if ( x < 0 || y < 0 || x >= w || y >= h) {
672 tiles->m_tiles.push_back(0);
673 } else {
674 tiles->m_tiles.push_back(tilemap->get_tile_id(x, y));
675 }
676 }
677 }
678}
679
680bool
681EditorOverlayWidget::on_mouse_button_up(const SDL_MouseButtonEvent& button)
682{
683 m_dragging = false;
684 return true;
685}
686
687bool
688EditorOverlayWidget::on_mouse_button_down(const SDL_MouseButtonEvent& button)
689{
690 switch (button.button)
691 {
692 case SDL_BUTTON_LEFT:
693 process_left_click();
694 return true;
695
696 case SDL_BUTTON_RIGHT:
697 process_right_click();
698 return true;
699
700 default:
701 return false;
702 }
703}
704
705bool
706EditorOverlayWidget::on_mouse_motion(const SDL_MouseMotionEvent& motion)
707{
708 m_mouse_pos = VideoSystem::current()->get_viewport().to_logical(motion.x, motion.y);
709 update_pos();
710
711 if (m_dragging)
712 {
713 switch (m_editor.get_tileselect_input_type())
714 {
715 case EditorToolboxWidget::InputType::TILE:
716 if (m_dragging_right) {
717 update_tile_selection();
718 } else {
719 switch (m_editor.get_tileselect_select_mode()) {
720 case 0:
721 put_tile();
722 break;
723 case 1:
724 draw_rectangle();
725 break;
726 default:
727 break;
728 }
729 }
730 break;
731
732 case EditorToolboxWidget::InputType::OBJECT:
733 if (m_editor.get_tileselect_object().empty()) {
734 if (m_editor.get_tileselect_select_mode() == 1) {
735 rubber_rect();
736 }
737 } else {
738 move_object();
739 }
740 break;
741
742 default:
743 break;
744 }
745 return true;
746 }
747 else
748 {
749 return false;
750 }
751}
752
753bool
754EditorOverlayWidget::on_key_up(const SDL_KeyboardEvent& key)
755{
756 auto sym = key.keysym.sym;
757 if (sym == SDLK_LSHIFT || sym == SDLK_RSHIFT)
758 {
759 snap_to_grid = !snap_to_grid;
760 }
761 return true;
762}
763
764bool
765EditorOverlayWidget::on_key_down(const SDL_KeyboardEvent& key)
766{
767 auto sym = key.keysym.sym;
768 if (sym == SDLK_F8) {
769 render_grid = !render_grid;
770 }
771 if (sym == SDLK_F7 || sym == SDLK_LSHIFT || sym == SDLK_RSHIFT) {
772 snap_to_grid = !snap_to_grid;
773 }
774 return true;
775}
776
777void
778EditorOverlayWidget::update_pos()
779{
780 if(m_editor.get_sector() == nullptr)
781 return;
782
783 m_sector_pos = m_mouse_pos + m_editor.get_sector()->get_camera().get_translation();
784 m_hovered_tile = sp_to_tp(m_sector_pos);
785 // update tip
786 hover_object();
787}
788
789void
790EditorOverlayWidget::draw_tile_tip(DrawingContext& context)
791{
792 if ( m_editor.get_tileselect_input_type() == EditorToolboxWidget::InputType::TILE ) {
793
794 auto tilemap = dynamic_cast<TileMap*>(m_editor.get_selected_tilemap());
795 if (!tilemap) {
796 return;
797 }
798
799 Vector drawn_tile = m_hovered_tile;
800 auto tiles = m_editor.get_tiles();
801
802 for (drawn_tile.x = static_cast<float>(tiles->m_width) - 1.0f; drawn_tile.x >= 0.0f; drawn_tile.x--) {
803 for (drawn_tile.y = static_cast<float>(tiles->m_height) - 1.0f; drawn_tile.y >= 0.0f; drawn_tile.y--) {
804 Vector on_tile = m_hovered_tile + drawn_tile;
805
806 if (m_editor.get_tiles()->empty() ||
807 on_tile.x < 0 ||
808 on_tile.y < 0 ||
809 on_tile.x >= static_cast<float>(tilemap->get_width()) ||
810 on_tile.y >= static_cast<float>(tilemap->get_height())) {
811 continue;
812 }
813 uint32_t tile_id = tiles->pos(static_cast<int>(drawn_tile.x), static_cast<int>(drawn_tile.y));
814 draw_tile(context.color(), *m_editor.get_tileset(), tile_id,
815 tp_to_sp(on_tile) - m_editor.get_sector()->get_camera().get_translation(),
816 LAYER_GUI-11, Color(1, 1, 1, 0.5));
817 /*if (tile_id) {
818 const Tile* tg_tile = m_editor.get_tileset()->get( tile_id );
819 tg_tile->draw(context.color(), tp_to_sp(on_tile) - m_editor.get_sector()->camera->get_translation(),
820 LAYER_GUI-11, Color(1, 1, 1, 0.5));
821 }*/
822 }
823 }
824 }
825}
826
827void
828EditorOverlayWidget::draw_tile_grid(DrawingContext& context, const Color& line_color, int tile_size)
829{
830 if ( !m_editor.get_selected_tilemap() ) {
831 return;
832 }
833
834 auto current_tm = dynamic_cast<TileMap*>(m_editor.get_selected_tilemap());
835 if ( current_tm == nullptr )
836 return;
837 int tm_width = current_tm->get_width() * (32 / tile_size);
838 int tm_height = current_tm->get_height() * (32 / tile_size);
839 auto cam_translation = m_editor.get_sector()->get_camera().get_translation();
840 Rectf draw_rect = Rectf(cam_translation, cam_translation +
841 Vector(static_cast<float>(context.get_width() - 128),
842 static_cast<float>(context.get_height() - 32)));
843 Vector start = sp_to_tp( Vector(draw_rect.get_left(), draw_rect.get_top()), tile_size );
844 Vector end = sp_to_tp( Vector(draw_rect.get_right(), draw_rect.get_bottom()), tile_size );
845 start.x = std::max(0.0f, start.x);
846 start.y = std::max(0.0f, start.y);
847 end.x = std::min(float(tm_width), end.x);
848 end.y = std::min(float(tm_height), end.y);
849
850 Vector line_start, line_end;
851 for (int i = static_cast<int>(start.x); i <= static_cast<int>(end.x); i++) {
852 line_start = tile_screen_pos( Vector(static_cast<float>(i), 0.0f), tile_size );
853 line_end = tile_screen_pos( Vector(static_cast<float>(i), end.y), tile_size );
854 context.color().draw_line(line_start, line_end, line_color, current_tm->get_layer());
855 }
856
857 for (int i = static_cast<int>(start.y); i <= static_cast<int>(end.y); i++) {
858 line_start = tile_screen_pos( Vector(0.0f, static_cast<float>(i)), tile_size );
859 line_end = tile_screen_pos( Vector(end.x, static_cast<float>(i)), tile_size );
860 context.color().draw_line(line_start, line_end, line_color, current_tm->get_layer());
861 }
862}
863
864void
865EditorOverlayWidget::draw_tilemap_border(DrawingContext& context)
866{
867 if ( !m_editor.get_selected_tilemap() ) return;
868
869 auto current_tm = dynamic_cast<TileMap*>(m_editor.get_selected_tilemap());
870 if ( !current_tm ) return;
871
872 Vector start = tile_screen_pos( Vector(0, 0) );
873 Vector end = tile_screen_pos( Vector(static_cast<float>(current_tm->get_width()),
874 static_cast<float>(current_tm->get_height())) );
875 context.color().draw_line(start, Vector(start.x, end.y), Color(1, 0, 1), current_tm->get_layer());
876 context.color().draw_line(start, Vector(end.x, start.y), Color(1, 0, 1), current_tm->get_layer());
877 context.color().draw_line(Vector(start.x, end.y), end, Color(1, 0, 1), current_tm->get_layer());
878 context.color().draw_line(Vector(end.x, start.y), end, Color(1, 0, 1), current_tm->get_layer());
879}
880
881void
882EditorOverlayWidget::draw_path(DrawingContext& context)
883{
884 if (!m_edited_path) return;
885 if (!m_selected_object) return;
886 if (!m_selected_object->is_valid()) return;
887 if (!m_edited_path->is_valid()) return;
888
889 for (auto i = m_edited_path->m_nodes.begin(); i != m_edited_path->m_nodes.end(); ++i) {
890 auto j = i+1;
891 Path::Node* node1 = &(*i);
892 Path::Node* node2;
893 if (j == m_edited_path->m_nodes.end()) {
894 if (m_edited_path->m_mode == WalkMode::CIRCULAR) {
895 //loop to the first node
896 node2 = &(*m_edited_path->m_nodes.begin());
897 } else {
898 continue;
899 }
900 } else {
901 node2 = &(*j);
902 }
903 auto cam_translation = m_editor.get_sector()->get_camera().get_translation();
904 context.color().draw_line(node1->position - cam_translation,
905 node2->position - cam_translation,
906 Color(1, 0, 0), LAYER_GUI - 21);
907 }
908}
909
910void
911EditorOverlayWidget::draw(DrawingContext& context)
912{
913 draw_tile_tip(context);
914 draw_path(context);
915
916 if (render_grid) {
917 draw_tile_grid(context, Color(1.f, 1.f, 1.f, 0.2f));
918 draw_tilemap_border(context);
919 auto snap_grid_size = snap_grid_sizes[selected_snap_grid_size];
920 if (snap_grid_size != 32) {
921 draw_tile_grid(context, Color(1.f, 1.f, 1.f, 0.2f), snap_grid_size);
922 }
923 }
924
925 if (m_object_tip) {
926 m_object_tip->draw(context, m_mouse_pos);
927 }
928
929 if (m_dragging && m_editor.get_tileselect_select_mode() == 1
930 && !m_dragging_right) {
931 // Draw selection rectangle...
932 auto cam_translation = m_editor.get_sector()->get_camera().get_translation();
933 Vector p0 = m_drag_start - cam_translation;
934 Vector p1 = Vector(m_drag_start.x, m_sector_pos.y) - cam_translation;
935 Vector p2 = Vector(m_sector_pos.x, m_drag_start.y) - cam_translation;
936
937 context.color().draw_filled_rect(Rectf(p0, p1 + Vector(2, 2)),
938 Color(0.0f, 1.0f, 0.0f, 1.0f), 0.0f, LAYER_GUI-5);
939 context.color().draw_filled_rect(Rectf(p2, m_mouse_pos + Vector(2, 2)),
940 Color(0.0f, 1.0f, 0.0f, 1.0f), 0.0f, LAYER_GUI-5);
941 context.color().draw_filled_rect(Rectf(p0, p2 + Vector(2, 2)),
942 Color(0.0f, 1.0f, 0.0f, 1.0f), 0.0f, LAYER_GUI-5);
943 context.color().draw_filled_rect(Rectf(p1, m_mouse_pos + Vector(2, 2)),
944 Color(0.0f, 1.0f, 0.0f, 1.0f), 0.0f, LAYER_GUI-5);
945
946 context.color().draw_filled_rect(Rectf(p0, m_mouse_pos),
947 Color(0.0f, 1.0f, 0.0f, 0.2f), 0.0f, LAYER_GUI-5);
948 }
949
950 if (m_dragging && m_dragging_right) {
951 context.color().draw_filled_rect(selection_draw_rect(),
952 Color(0.2f, 0.4f, 1.0f, 0.6f), 0.0f, LAYER_GUI-13);
953 }
954}
955
956Vector
957EditorOverlayWidget::tp_to_sp(const Vector& tp, int tile_size)
958{
959 auto tilemap = dynamic_cast<TileMap*>(m_editor.get_selected_tilemap());
960 if (!tilemap)
961 {
962 return Vector(0, 0);
963 }
964
965 Vector sp = tp * static_cast<float>(tile_size);
966 return sp + tilemap->get_offset();
967}
968
969Vector
970EditorOverlayWidget::sp_to_tp(const Vector& sp, int tile_size)
971{
972 auto tilemap = dynamic_cast<TileMap*>(m_editor.get_selected_tilemap());
973 if (!tilemap)
974 {
975 return Vector(0, 0);
976 }
977
978 Vector sp_ = sp - tilemap->get_offset();
979 return sp_ / static_cast<float>(tile_size);
980}
981
982Vector
983EditorOverlayWidget::tile_screen_pos(const Vector& tp, int tile_size)
984{
985 Vector sp = tp_to_sp(tp, tile_size);
986 return sp - m_editor.get_sector()->get_camera().get_translation();
987}
988
989/* EOF */
990